diff --git a/.gitignore b/.gitignore
index e5f481e..489d3b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,3 +141,5 @@ reference/
# Example results
examples/*/config/
+test/example_init_shot/*/
+test/example_init_shot/data.out
diff --git a/test/example_init_shot/parameter_output_1.yaml b/test/example_init_shot/parameter_output_1.yaml
new file mode 100644
index 0000000..e1e91c6
--- /dev/null
+++ b/test/example_init_shot/parameter_output_1.yaml
@@ -0,0 +1,14 @@
+
+iters: 20
+
+optimiser: random
+
+parameters:
+ Young2: [20, 10, 30]
+
+output_parameters:
+ Young: Young2*10
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/example_init_shot/parameter_output_2.yaml b/test/example_init_shot/parameter_output_2.yaml
new file mode 100644
index 0000000..d101c79
--- /dev/null
+++ b/test/example_init_shot/parameter_output_2.yaml
@@ -0,0 +1,16 @@
+
+iters: 20
+
+optimiser: random
+
+parameters:
+ Young2: [20, 10, 30]
+
+output_parameters:
+ Young: Young2*10
+
+init_shot_from: parameter_output_1.yaml
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/example_init_shot/parameter_output_3.yaml b/test/example_init_shot/parameter_output_3.yaml
new file mode 100644
index 0000000..423dcf9
--- /dev/null
+++ b/test/example_init_shot/parameter_output_3.yaml
@@ -0,0 +1,20 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: ucb
+ beta: 0.9
+ n_test: 2
+ export: data.out
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
+
diff --git a/test/example_init_shot/parameter_output_4.yaml b/test/example_init_shot/parameter_output_4.yaml
new file mode 100644
index 0000000..d519421
--- /dev/null
+++ b/test/example_init_shot/parameter_output_4.yaml
@@ -0,0 +1,20 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: ucb
+ beta: 0.9
+ n_test: 2
+ load_file: data.out
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
+
diff --git a/test/examples/bo_1call_ei.yaml b/test/examples/bo_1call_ei.yaml
new file mode 100644
index 0000000..bd3fbfc
--- /dev/null
+++ b/test/examples/bo_1call_ei.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: ei
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/bo_1call_kg.yaml b/test/examples/bo_1call_kg.yaml
new file mode 100644
index 0000000..3ae2aaa
--- /dev/null
+++ b/test/examples/bo_1call_kg.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: kg
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/bo_1call_pi.yaml b/test/examples/bo_1call_pi.yaml
new file mode 100644
index 0000000..67815a1
--- /dev/null
+++ b/test/examples/bo_1call_pi.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: pi
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/bo_1call_qei.yaml b/test/examples/bo_1call_qei.yaml
new file mode 100644
index 0000000..d7726fe
--- /dev/null
+++ b/test/examples/bo_1call_qei.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: qei
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/bo_1call_qkg.yaml b/test/examples/bo_1call_qkg.yaml
new file mode 100644
index 0000000..dc0a253
--- /dev/null
+++ b/test/examples/bo_1call_qkg.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: qkg
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/bo_1call_qpi.yaml b/test/examples/bo_1call_qpi.yaml
new file mode 100644
index 0000000..9017a36
--- /dev/null
+++ b/test/examples/bo_1call_qpi.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: qpi
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/bo_1call_qucb.yaml b/test/examples/bo_1call_qucb.yaml
new file mode 100644
index 0000000..e40bcbd
--- /dev/null
+++ b/test/examples/bo_1call_qucb.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: qucb
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples/design_integral.yaml b/test/examples/design_integral.yaml
new file mode 100644
index 0000000..8a8ff03
--- /dev/null
+++ b/test/examples/design_integral.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'integral_quantity':
+ quantity: integral
+ prediction: ['case_1']
+ negate: False
diff --git a/test/examples/design_missing_quantity_script.yaml b/test/examples/design_missing_quantity_script.yaml
new file mode 100644
index 0000000..f93ed14
--- /dev/null
+++ b/test/examples/design_missing_quantity_script.yaml
@@ -0,0 +1,29 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'script_quantity':
+ quantity:
+ name: script
+ script: min_script.py # Path to the script
+ class: MinQuantity # Class name (must be derived from Quantity)
+ prediction: ['case_1']
+ negate: True
diff --git a/test/examples/dummy_composite_ei.yaml b/test/examples/dummy_composite_ei.yaml
new file mode 100644
index 0000000..8bc8a86
--- /dev/null
+++ b/test/examples/dummy_composite_ei.yaml
@@ -0,0 +1,27 @@
+
+iters: 10
+
+optimiser:
+ name: botorch
+ acquisition: ei
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+
+objective:
+ name: fitting
+ composite: True
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1']
diff --git a/test/examples/dummy_composite_pi.yaml b/test/examples/dummy_composite_pi.yaml
new file mode 100644
index 0000000..c71abb1
--- /dev/null
+++ b/test/examples/dummy_composite_pi.yaml
@@ -0,0 +1,27 @@
+
+iters: 10
+
+optimiser:
+ name: botorch
+ acquisition: pi
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+
+objective:
+ name: fitting
+ composite: True
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1']
diff --git a/test/examples/dummy_design_pred_str.yaml b/test/examples/dummy_design_pred_str.yaml
new file mode 100644
index 0000000..6360b09
--- /dev/null
+++ b/test/examples/dummy_design_pred_str.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'maximum_force':
+ quantity: max
+ prediction: case_1
+ negate: False
diff --git a/test/examples/dummy_simple_pred_str.yaml b/test/examples/dummy_simple_pred_str.yaml
new file mode 100644
index 0000000..aaf7a02
--- /dev/null
+++ b/test/examples/dummy_simple_pred_str.yaml
@@ -0,0 +1,25 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+
+objective:
+ name: fitting
+ composite: False
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: case_1
diff --git a/test/examples/min_script.py b/test/examples/min_script.py
new file mode 100644
index 0000000..be4557d
--- /dev/null
+++ b/test/examples/min_script.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+import numpy as np
+from piglot.solver.solver import OutputResult
+from piglot.objectives.design import Quantity
+
+class MinQuantity(Quantity):
+ """Minimum value of a response."""
+
+ def compute(self, result: OutputResult) -> float:
+ """Get the minimum of a given response.
+
+ Parameters
+ ----------
+ result : OutputResult
+ Output result to compute the quantity for.
+
+ Returns
+ -------
+ float
+ Quantity value.
+ """
+ return np.min(result.get_data())
diff --git a/test/examples/test_analytical_parallel.yaml b/test/examples/test_analytical_parallel.yaml
new file mode 100644
index 0000000..1c8a517
--- /dev/null
+++ b/test/examples/test_analytical_parallel.yaml
@@ -0,0 +1,18 @@
+
+
+iters: 0
+
+
+optimiser:
+ name: botorch
+ q: 2
+ acquisition: qucb
+
+parameters:
+ Young: [200, 100, 300]
+ poisson: [0.25, 0.1, 0.4]
+
+
+objective:
+ name: analytical
+ expression: -((Young/poisson)-500)**2
diff --git a/test/examples_assertions/bo_equalbounds.yaml b/test/examples_assertions/bo_equalbounds.yaml
new file mode 100644
index 0000000..3052721
--- /dev/null
+++ b/test/examples_assertions/bo_equalbounds.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: ucb
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [300, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: 1
diff --git a/test/examples_assertions/bo_q.yaml b/test/examples_assertions/bo_q.yaml
new file mode 100644
index 0000000..7203b29
--- /dev/null
+++ b/test/examples_assertions/bo_q.yaml
@@ -0,0 +1,19 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: ucb
+ beta: 0.9
+ n_test: 2
+ q: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples_assertions/bo_unkacq.yaml b/test/examples_assertions/bo_unkacq.yaml
new file mode 100644
index 0000000..9f02528
--- /dev/null
+++ b/test/examples_assertions/bo_unkacq.yaml
@@ -0,0 +1,18 @@
+
+iters: 2
+max_func_calls: 0
+
+optimiser:
+ name: botorch
+ acquisition: ucbb
+ beta: 0.9
+ n_test: 2
+
+
+parameters:
+ Young: [200, 100, 300]
+
+
+objective:
+ name: analytical
+ expression: (Young-150)**2
diff --git a/test/examples_assertions/design_missing_pred.yaml b/test/examples_assertions/design_missing_pred.yaml
new file mode 100644
index 0000000..17b0d67
--- /dev/null
+++ b/test/examples_assertions/design_missing_pred.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'maximum_force':
+ quantity: max
+ # prediction: ['case_1']
+ negate: False
diff --git a/test/examples_assertions/design_missing_quantity.yaml b/test/examples_assertions/design_missing_quantity.yaml
new file mode 100644
index 0000000..5fb8580
--- /dev/null
+++ b/test/examples_assertions/design_missing_quantity.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'maximum_force':
+ # quantity: max
+ prediction: ['case_1']
+ negate: False
diff --git a/test/examples_assertions/design_missing_quantity_class.yaml b/test/examples_assertions/design_missing_quantity_class.yaml
new file mode 100644
index 0000000..93dccc0
--- /dev/null
+++ b/test/examples_assertions/design_missing_quantity_class.yaml
@@ -0,0 +1,29 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'script_quantity':
+ quantity:
+ name: script
+ script: quantity_script.py # Path to the script
+ # class: QuantityClass # Class name (must be derived from Quantity)
+ prediction: ['case_1']
+ negate: True
diff --git a/test/examples_assertions/design_missing_quantity_name.yaml b/test/examples_assertions/design_missing_quantity_name.yaml
new file mode 100644
index 0000000..27876a3
--- /dev/null
+++ b/test/examples_assertions/design_missing_quantity_name.yaml
@@ -0,0 +1,29 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'script_quantity':
+ quantity:
+ # name: script
+ script: quantity_script.py # Path to the script
+ class: QuantityClass # Class name (must be derived from Quantity)
+ prediction: ['case_1']
+ negate: True
diff --git a/test/examples_assertions/design_missing_quantity_script.yaml b/test/examples_assertions/design_missing_quantity_script.yaml
new file mode 100644
index 0000000..ca7c0b1
--- /dev/null
+++ b/test/examples_assertions/design_missing_quantity_script.yaml
@@ -0,0 +1,29 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'script_quantity':
+ quantity:
+ name: script
+ # script: quantity_script.py # Path to the script
+ class: QuantityClass # Class name (must be derived from Quantity)
+ prediction: ['case_1']
+ negate: True
diff --git a/test/examples_assertions/design_missing_solver.yaml b/test/examples_assertions/design_missing_solver.yaml
new file mode 100644
index 0000000..085f945
--- /dev/null
+++ b/test/examples_assertions/design_missing_solver.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ # solver:
+ # name: curve
+ # cases:
+ # 'case_1':
+ # expression: * x ** 2
+ # parametric: x
+ # bounds: [-5, 5]
+ # points: 100
+ targets:
+ 'maximum_force':
+ quantity: max
+ prediction: ['case_1']
+ negate: False
diff --git a/test/examples_assertions/design_missing_targets.yaml b/test/examples_assertions/design_missing_targets.yaml
new file mode 100644
index 0000000..8d5b26f
--- /dev/null
+++ b/test/examples_assertions/design_missing_targets.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ # targets:
+ # 'maximum_force':
+ # quantity: max
+ # prediction: ['case_1']
+ # negate: False
diff --git a/test/examples_assertions/duplicated_field.yaml b/test/examples_assertions/duplicated_field.yaml
new file mode 100644
index 0000000..1bde6da
--- /dev/null
+++ b/test/examples_assertions/duplicated_field.yaml
@@ -0,0 +1,45 @@
+
+iters: 50
+
+optimiser:
+ name: botorch
+ n_initial: 1
+ n_test: 0
+ beta: 1
+ acquisition: ucb
+
+
+
+parameters:
+ Young: [300, 100, 300]
+
+
+objective:
+ name: fitting
+ solver:
+ name: links
+ links: LINKS
+ cases:
+ 'prediction_1d.dat':
+ fields:
+ 'reaction_x':
+ name: Reaction
+ field: x
+ 'reaction_y':
+ name: Reaction
+ field: y
+ 'prediction_1d_1.dat':
+ fields:
+ 'reaction_x':
+ name: Reaction
+ field: x
+ 'reaction_y_1':
+ name: Reaction
+ field: y
+ references:
+ 'ref_x.reac':
+ prediction: ['reaction_x', 'reaction_x_1']
+ filter_tol: 1e-6
+ 'ref_y.reac':
+ prediction: ['reaction_y', 'reaction_y_1']
+ filter_tol: 1e-6
diff --git a/test/examples_assertions/fitting_without_solver.yaml b/test/examples_assertions/fitting_without_solver.yaml
new file mode 100644
index 0000000..018bd95
--- /dev/null
+++ b/test/examples_assertions/fitting_without_solver.yaml
@@ -0,0 +1,33 @@
+
+iters: 10
+
+optimiser:
+ name: botorch
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+objective:
+ name: fitting
+ composite: True
+ stochastic: True
+ # solver:
+ # name: curve
+ # cases:
+ # 'case_1':
+ # expression: * x ** 2
+ # parametric: x
+ # bounds: [-5, 5]
+ # points: 100
+ # 'case_2':
+ # expression: 1.1 * * x ** 2
+ # parametric: x
+ # bounds: [-5, 5]
+ # points: 100
+
+
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1', 'case_2']
diff --git a/test/examples_assertions/invalid_pred.yaml b/test/examples_assertions/invalid_pred.yaml
new file mode 100644
index 0000000..7ee164c
--- /dev/null
+++ b/test/examples_assertions/invalid_pred.yaml
@@ -0,0 +1,20 @@
+iters: 10
+
+optimiser: botorch
+
+parameters:
+ a: [1, 0, 4]
+
+objective:
+ name: fitting
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: 2
\ No newline at end of file
diff --git a/test/examples_assertions/invalid_pred_design.yaml b/test/examples_assertions/invalid_pred_design.yaml
new file mode 100644
index 0000000..9b2e559
--- /dev/null
+++ b/test/examples_assertions/invalid_pred_design.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'maximum_force':
+ quantity: max
+ prediction: 2
+ negate: False
diff --git a/test/examples_assertions/mae_reduction.yaml b/test/examples_assertions/mae_reduction.yaml
new file mode 100644
index 0000000..5314d17
--- /dev/null
+++ b/test/examples_assertions/mae_reduction.yaml
@@ -0,0 +1,34 @@
+
+iters: 10
+
+optimiser:
+ name: botorch
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+objective:
+ name: fitting
+ composite: True
+ stochastic: True
+ reduction: mae
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ 'case_2':
+ expression: 1.1 * * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+
+
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1', 'case_2']
diff --git a/test/examples_assertions/missing_pred.yaml b/test/examples_assertions/missing_pred.yaml
new file mode 100644
index 0000000..df4d999
--- /dev/null
+++ b/test/examples_assertions/missing_pred.yaml
@@ -0,0 +1,26 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+
+objective:
+ name: fitting
+ composite: False
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ asd: False
+ # prediction: ['case_1']
diff --git a/test/examples_assertions/missing_references.yaml b/test/examples_assertions/missing_references.yaml
new file mode 100644
index 0000000..ade2f7b
--- /dev/null
+++ b/test/examples_assertions/missing_references.yaml
@@ -0,0 +1,33 @@
+
+iters: 10
+
+optimiser:
+ name: botorch
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+objective:
+ name: fitting
+ composite: True
+ stochastic: True
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ 'case_2':
+ expression: 1.1 * * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+
+
+ # references:
+ # 'reference_curve.txt':
+ # prediction: ['case_1', 'case_2']
diff --git a/test/examples_assertions/missing_solver_name.yaml b/test/examples_assertions/missing_solver_name.yaml
new file mode 100644
index 0000000..cb73986
--- /dev/null
+++ b/test/examples_assertions/missing_solver_name.yaml
@@ -0,0 +1,20 @@
+iters: 10
+
+optimiser: botorch
+
+parameters:
+ a: [1, 0, 4]
+
+objective:
+ name: fitting
+ solver:
+ #name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1']
\ No newline at end of file
diff --git a/test/examples_assertions/reference_curve_2.txt b/test/examples_assertions/reference_curve_2.txt
new file mode 100644
index 0000000..3d109ab
--- /dev/null
+++ b/test/examples_assertions/reference_curve_2.txt
@@ -0,0 +1,6 @@
+-4.0 32.0
+-2.4 11.52
+-0.7999999999999998 1.2799999999999994
+0.8000000000000007 1.2800000000000022
+2.4000000000000004 11.520000000000003
+4.0 32.0
diff --git a/test/examples_assertions/unexistent_reference.yaml b/test/examples_assertions/unexistent_reference.yaml
new file mode 100644
index 0000000..7d82bad
--- /dev/null
+++ b/test/examples_assertions/unexistent_reference.yaml
@@ -0,0 +1,27 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+
+objective:
+ name: fitting
+ composite: False
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1']
+ 'reference_curve_2.txt':
+ prediction: ['case_2']
diff --git a/test/examples_assertions/unexistent_targets.yaml b/test/examples_assertions/unexistent_targets.yaml
new file mode 100644
index 0000000..21fc990
--- /dev/null
+++ b/test/examples_assertions/unexistent_targets.yaml
@@ -0,0 +1,30 @@
+
+iters: 10
+
+optimiser: random
+
+
+parameters:
+ a: [2, 0.5, 4]
+
+
+
+objective:
+ name: design
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ targets:
+ 'maximum_force':
+ quantity: max
+ prediction: ['case_1']
+ negate: False
+ 'integral_quantity':
+ quantity: integral
+ prediction: ['case_2']
+ negate: False
diff --git a/test/examples_assertions/wrong_solver_name.yaml b/test/examples_assertions/wrong_solver_name.yaml
new file mode 100644
index 0000000..be955a6
--- /dev/null
+++ b/test/examples_assertions/wrong_solver_name.yaml
@@ -0,0 +1,20 @@
+iters: 10
+
+optimiser: botorch
+
+parameters:
+ a: [1, 0, 4]
+
+objective:
+ name: fitting
+ solver:
+ name: curvefit
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ references:
+ 'reference_curve.txt':
+ prediction: ['case_1']
\ No newline at end of file
diff --git a/test/examples_plots/dummy_composite_stochastic_design.yaml b/test/examples_plots/dummy_composite_stochastic_design.yaml
new file mode 100644
index 0000000..d944514
--- /dev/null
+++ b/test/examples_plots/dummy_composite_stochastic_design.yaml
@@ -0,0 +1,36 @@
+
+iters: 10
+
+optimiser:
+ name: botorch
+
+
+parameters:
+ a: [0, -4, 4]
+
+
+objective:
+ name: design
+ composite: True
+ stochastic: True
+ solver:
+ name: curve
+ cases:
+ 'case_1':
+ expression: * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+ 'case_2':
+ expression: 1.1 * * x ** 2
+ parametric: x
+ bounds: [-5, 5]
+ points: 100
+
+
+ targets:
+ 'maximum_force':
+ quantity: max
+ prediction: ['case_1', 'case_2']
+ negate: False
+
diff --git a/test/examples_plots_assertions/analytical_parameters_3.yaml b/test/examples_plots_assertions/analytical_parameters_3.yaml
new file mode 100644
index 0000000..d6271b2
--- /dev/null
+++ b/test/examples_plots_assertions/analytical_parameters_3.yaml
@@ -0,0 +1,17 @@
+
+
+iters: 1
+
+
+optimiser: random
+
+
+parameters:
+ a: [200, 100, 300]
+ b: [0.25, 0.1, 0.4]
+ c: [0.025, 0.01, 0.04]
+
+
+objective:
+ name: analytical
+ expression: ((a/b)-500)**2 + c**2
diff --git a/test/examples_plots_assertions/gp_error.yaml b/test/examples_plots_assertions/gp_error.yaml
new file mode 100644
index 0000000..2effc38
--- /dev/null
+++ b/test/examples_plots_assertions/gp_error.yaml
@@ -0,0 +1,16 @@
+
+
+iters: 1
+
+
+optimiser: random
+
+
+parameters:
+ Young: [200, 100, 300]
+ poisson: [0.25, 0.1, 0.4]
+
+
+objective:
+ name: analytical
+ expression: ((Young/poisson)-500)**2
diff --git a/test/solver_utils/input_file_1.txt b/test/solver_utils/input_file_1.txt
new file mode 100644
index 0000000..d89a624
--- /dev/null
+++ b/test/solver_utils/input_file_1.txt
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/test/solver_utils/input_file_2.txt b/test/solver_utils/input_file_2.txt
new file mode 100644
index 0000000..ef5ceca
--- /dev/null
+++ b/test/solver_utils/input_file_2.txt
@@ -0,0 +1 @@
+
diff --git a/test/solver_utils/input_file_3.txt b/test/solver_utils/input_file_3.txt
new file mode 100644
index 0000000..8433c54
--- /dev/null
+++ b/test/solver_utils/input_file_3.txt
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/test/solver_utils/test_module.py b/test/solver_utils/test_module.py
new file mode 100644
index 0000000..8724927
--- /dev/null
+++ b/test/solver_utils/test_module.py
@@ -0,0 +1,3 @@
+# test_module.py
+
+test_attribute = "CM2S"
\ No newline at end of file
diff --git a/test/test_examples_assertions.py b/test/test_examples_assertions.py
index e052da6..aaa017a 100644
--- a/test/test_examples_assertions.py
+++ b/test/test_examples_assertions.py
@@ -80,6 +80,90 @@
RuntimeError,
"Failed to parse the config file: YAML syntax seems invalid.",
),
+ 'missing_solver_name.yaml': (
+ ValueError,
+ "Missing name for solver.",
+ ),
+ 'wrong_solver_name.yaml': (
+ ValueError,
+ "Unknown solver 'curvefit'.",
+ ),
+ 'fitting_without_solver.yaml': (
+ ValueError,
+ "Missing solver for fitting objective.",
+ ),
+ 'invalid_pred.yaml': (
+ ValueError,
+ "Invalid prediction '2' for reference 'reference_curve.txt'.",
+ ),
+ 'invalid_pred_design.yaml': (
+ ValueError,
+ "Invalid prediction '2' for design target 'maximum_force'.",
+ ),
+ 'missing_references.yaml': (
+ ValueError,
+ "Missing references for fitting objective.",
+ ),
+ 'missing_pred.yaml': (
+ ValueError,
+ "Missing prediction for reference 'reference_curve.txt'.",
+ ),
+ 'unexistent_reference.yaml': (
+ ValueError,
+ "Reference 'reference_curve_2.txt' is not associated to any case.",
+ ),
+ 'mae_reduction.yaml': (
+ ValueError,
+ "Invalid reduction 'mae' for fitting objective.",
+ ),
+ 'design_missing_pred.yaml': (
+ ValueError,
+ "Missing prediction for design target 'maximum_force'.",
+ ),
+ 'design_missing_quantity.yaml': (
+ ValueError,
+ "Missing quantity for fitting objective.",
+ ),
+ 'design_missing_quantity_name.yaml': (
+ ValueError,
+ "Missing name in quantity specification.",
+ ),
+ 'design_missing_quantity_script.yaml': (
+ ValueError,
+ "Missing script in quantity specification.",
+ ),
+ 'design_missing_quantity_class.yaml': (
+ ValueError,
+ "Missing class in quantity specification.",
+ ),
+ 'design_missing_solver.yaml': (
+ ValueError,
+ "Missing solver for fitting objective.",
+ ),
+ 'design_missing_targets.yaml': (
+ ValueError,
+ "Missing targets for fitting objective.",
+ ),
+ 'unexistent_targets.yaml': (
+ ValueError,
+ "Design target 'integral_quantity' is not associated to any case.",
+ ),
+ 'bo_unkacq.yaml': (
+ RuntimeError,
+ "Unkown acquisition function ucbb",
+ ),
+ 'bo_q.yaml': (
+ RuntimeError,
+ "Can only use q != 1 for quasi-Monte Carlo acquisitions",
+ ),
+ 'duplicated_field.yaml': (
+ ValueError,
+ "Duplicate output field 'reaction_x'.",
+ ),
+ 'bo_equalbounds.yaml': (
+ RuntimeError,
+ "All observed points are equal: add more initial samples",
+ ),
}
diff --git a/test/test_examples_plots.py b/test/test_examples_plots.py
index a2c8651..67d0b0b 100644
--- a/test/test_examples_plots.py
+++ b/test/test_examples_plots.py
@@ -8,12 +8,43 @@
def get_files(path: str) -> List[str]:
+ """Gets all the files in a directory.
+
+ Parameters
+ ----------
+ path : str
+ Path to the directory.
+
+ Returns
+ -------
+ List[str]
+ List of files in the directory.
+ """
return [
os.path.join(path, file)
for file in os.listdir(path)
if os.path.isfile(os.path.join(path, file)) and file.endswith('.yaml')
]
+def get_first_hash(filename: str) -> str:
+ """Extracts the first hash from a file.
+
+ Parameters
+ ----------
+ filename : str
+ Path to the file.
+
+ Returns
+ -------
+ str
+ The first hash in the file.
+ """
+ with open(filename, 'r', encoding='utf-8') as file:
+ next(file) # Skip the header line
+ first_line = next(file) # Get the second line
+ first_hash = first_line.split()[-1] # The hash is the last element on the line
+ return first_hash
+
@pytest.mark.parametrize('input_dir', get_files('test/examples_plots'))
def test_input_files(input_dir: str):
@@ -21,6 +52,8 @@ def test_input_files(input_dir: str):
input_file = os.path.basename(input_dir)
output_dir, _ = os.path.splitext(input_file)
piglot_main(input_file)
+ filename = os.path.join(output_dir, 'func_calls')
+ first_hash = get_first_hash(filename)
for kind in ('best', 'history', 'parameters', 'regret'):
piglot_plot_main([
kind,
@@ -46,5 +79,25 @@ def test_input_files(input_dir: str):
'--log',
'--time',
])
+ piglot_plot_main([
+ 'case',
+ input_file,
+ first_hash,
+ '--save_fig',
+ os.path.join(output_dir, 'case.png'),
+ ])
+ piglot_plot_main([
+ 'animation',
+ input_file,
+ '--save_fig',
+ os.path.join(output_dir, 'animation.png'),
+ ])
+ if input_file != 'test_analytical.yaml':
+ piglot_plot_main([
+ 'gp',
+ input_file,
+ '--save_fig',
+ os.path.join(output_dir, 'gp.png'),
+ ])
if os.path.isdir(output_dir):
shutil.rmtree(output_dir)
diff --git a/test/test_examples_plots_assertions.py b/test/test_examples_plots_assertions.py
new file mode 100644
index 0000000..95f746c
--- /dev/null
+++ b/test/test_examples_plots_assertions.py
@@ -0,0 +1,67 @@
+import unittest
+import os
+import shutil
+from piglot.bin.piglot import main as piglot_main
+from piglot.bin.piglot_plot import main as piglot_plot_main
+
+def get_first_hash(filename: str) -> str:
+ """Extracts the first hash from a file.
+
+ Parameters
+ ----------
+ filename : str
+ Path to the file.
+
+ Returns
+ -------
+ str
+ The first hash in the file.
+ """
+ with open(filename, 'r', encoding='utf-8') as file:
+ next(file) # Skip the header line
+ first_line = next(file) # Get the second line
+ first_hash = first_line.split()[-1] # The hash is the last element on the line
+ return first_hash
+
+class TestExtractParameters(unittest.TestCase):
+ def test_analytical_error(self):
+ with self.assertRaises(RuntimeError) as ex:
+ input_file = os.path.join("test", "examples_plots_assertions", \
+ "analytical_parameters_3.yaml")
+ output_dir = os.path.join("test", "examples_plots_assertions", \
+ "analytical_parameters_3")
+ piglot_main(input_file)
+ filename = os.path.join(output_dir, 'func_calls')
+ first_hash = get_first_hash(filename)
+ piglot_plot_main([
+ 'case',
+ input_file,
+ first_hash,
+ '--save_fig',
+ os.path.join(output_dir, 'case_analytical.png'),
+ ])
+ if os.path.isdir(output_dir):
+ shutil.rmtree(output_dir)
+ self.assertEqual(str(ex.exception), "Plotting not supported for 3 or more parameters.")
+
+ def test_gp_single_parameter(self):
+ with self.assertRaises(ValueError) as ex:
+ input_file = os.path.join("test", "examples_plots_assertions", \
+ "gp_error.yaml")
+ output_dir = os.path.join("test", "examples_plots_assertions", \
+ "gp_error")
+ piglot_main(input_file)
+ piglot_plot_main([
+ 'gp',
+ input_file,
+ '--save_fig',
+ os.path.join(output_dir, 'gp_error.png'),
+ ])
+ if os.path.isdir(output_dir):
+ shutil.rmtree(output_dir)
+ self.assertEqual(str(ex.exception), \
+ "Can only plot a Gaussian process regression for a single parameter.")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test_init_shot.py b/test/test_init_shot.py
new file mode 100644
index 0000000..3927ce2
--- /dev/null
+++ b/test/test_init_shot.py
@@ -0,0 +1,18 @@
+from typing import List
+import os
+import pytest
+from piglot.bin.piglot import main as piglot_main
+from piglot.utils.assorted import change_cwd
+
+def get_files(path: str) -> List[str]:
+ return [
+ os.path.join(path, file)
+ for file in os.listdir(path)
+ if os.path.isfile(os.path.join(path, file)) and file.endswith('.yaml')
+ ]
+
+@pytest.mark.parametrize('input_dir', get_files('test/example_init_shot'))
+def test_input_files(input_dir: str):
+ with change_cwd(os.path.dirname(input_dir)):
+ input_file = os.path.basename(input_dir)
+ piglot_main(input_file)
diff --git a/test/test_solver_utils.py b/test/test_solver_utils.py
new file mode 100644
index 0000000..ceee937
--- /dev/null
+++ b/test/test_solver_utils.py
@@ -0,0 +1,77 @@
+import unittest
+import os
+from piglot.utils.solver_utils import extract_parameters, has_keyword, has_parameter, find_keyword, load_module_from_file
+from piglot.parameter import ParameterSet
+
+INPUT_FILE_1 = os.path.join("test", "solver_utils", "input_file_1.txt")
+INPUT_FILE_2 = os.path.join("test", "solver_utils", "input_file_2.txt")
+INPUT_FILE_3 = os.path.join("test", "solver_utils", "input_file_3.txt")
+INPUT_FILE_4 = os.path.join("test", "solver_utils", "test_module.py")
+
+class TestExtractParameters(unittest.TestCase):
+ def test_extract_parameters(self):
+ # Call the function with the test input file
+ parameters = extract_parameters(INPUT_FILE_1)
+
+ # Verify the returned ParameterSet
+ self.assertIsInstance(parameters, ParameterSet)
+ self.assertEqual(len(parameters), 2)
+ self.assertEqual(parameters[0].inital_value, 1.0)
+ self.assertEqual(parameters[0].lbound, 0.0)
+ self.assertEqual(parameters[0].ubound, 2.0)
+ self.assertEqual(parameters[1].inital_value, 2.0)
+ self.assertEqual(parameters[1].lbound, 1.0)
+ self.assertEqual(parameters[1].ubound, 3.0)
+
+ def test_extract_parameters_fail_1(self):
+ with self.assertRaises(RuntimeError) as ex:
+ extract_parameters(INPUT_FILE_2)
+ self.assertEqual(str(ex.exception), "Pattern parameter1 referenced but not defined!")
+
+ def test_extract_parameters_fail_2(self):
+ with self.assertRaises(RuntimeError) as ex:
+ extract_parameters(INPUT_FILE_3)
+ self.assertEqual(str(ex.exception), "Repeated pattern parameter1 in file!")
+
+ def test_has_keyword(self):
+ # Call the function with the test input file and a keyword that is in the file
+ result = has_keyword(INPUT_FILE_1, '')
+
+ def test_find_keyword_fail(self):
+ with self.assertRaises(RuntimeError) as ex:
+ find_keyword(INPUT_FILE_1, '