climate_ref.solver
#
Solver to determine which diagnostics need to be calculated
This module provides a solver to determine which diagnostics need to be calculated.
DiagnosticExecution
#
Class to hold information about the execution of a diagnostic
This is a temporary class used by the solver to hold information about an execution that might be required.
Source code in packages/climate-ref/src/climate_ref/solver.py
dataset_key
property
#
Key used to uniquely identify the execution group
This key is unique to an execution group and uses unique set of metadata (selectors) that defines the group. This key is combines the selectors from each source dataset type into a single key and should be stable if new datasets are added or removed.
selectors
property
#
Collection of selectors used to identify the datasets
These are the key, value pairs that were selected during the initial group-by, for each data requirement.
build_execution_definition(output_root)
#
Build the execution definition for the current diagnostic execution.
The returned definition uses a placeholder fragment for the output directory.
solve_required_executions rewrites output_directory via
:func:attrs.evolve once the new Execution.id is known.
Source code in packages/climate-ref/src/climate_ref/solver.py
ExecutionSolver
#
A solver to determine which executions need to be calculated.
Source code in packages/climate-ref/src/climate_ref/solver.py
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 | |
build_from_db(config, db)
staticmethod
#
Initialise the solver using information from the database
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
Database
|
Database instance |
required |
Returns:
| Type | Description |
|---|---|
ExecutionSolver
|
A new ExecutionSolver instance |
Source code in packages/climate-ref/src/climate_ref/solver.py
solve(filters=None)
#
Solve which executions need to be calculated for a dataset
The solving scheme is iterative, for each iteration we find all diagnostics that can be solved and calculate them. After each iteration we check if there are any more diagnostics to solve.
Yields:
| Type | Description |
|---|---|
DiagnosticExecution
|
A class containing the information related to the execution of a diagnostic |
Source code in packages/climate-ref/src/climate_ref/solver.py
SolveFilterOptions
#
Options to filter the diagnostics that are solved
Source code in packages/climate-ref/src/climate_ref/solver.py
dataset = None
class-attribute
instance-attribute
#
Filter datasets by facet values before solving.
Keys are facet names (e.g. source_id, variable_id) and values are
lists of allowed values. Different facets are ANDed together; multiple
values for the same facet are ORed.
diagnostic = None
class-attribute
instance-attribute
#
Check if the diagnostic slug contains any of the provided values
exclude_diagnostic = None
class-attribute
instance-attribute
#
Exclude diagnostics by slug or provider/diagnostic pair.
Matching is exact and case-insensitive, unlike the fuzzy include filters.
This uses the same token format as the REF_TEST_CASES_SKIP
environment variable.
exclude_provider = None
class-attribute
instance-attribute
#
Exclude providers by exact, case-insensitive slug.
provider = None
class-attribute
instance-attribute
#
Check if the provider slug contains any of the provided values
SolveOutcome
#
What went wrong in a solve, so the caller can exit non-zero.
Source code in packages/climate-ref/src/climate_ref/solver.py
failed_executions = 0
class-attribute
instance-attribute
#
Executions submitted by this solve that had failed when it finished.
Always 0 unless the solve waited, because results are only known after waiting.
Undercounts when a result is recorded after join returns, eg. a slow Celery result callback.
ok
property
#
Whether every provider was healthy and no submitted execution failed
unhealthy_providers = attrs.field(factory=list)
class-attribute
instance-attribute
#
Slugs of the providers left out because they failed their health check
apply_dataset_filters(data_catalog, dataset_filters)
#
Filter data catalogs by facet values
Each facet filter is applied independently to each data catalog. Different facets are ANDed together; multiple values for the same facet are ORed. Facets that do not exist as columns in a given catalog are skipped for that catalog.
When a DataCatalog is provided, the returned value preserves the DataCatalog wrapper (with adapter and database references) so that downstream finalisation still works.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_catalog
|
Mapping[SourceDatasetType, DataCatalog | DataFrame]
|
Data catalogs keyed by source dataset type |
required |
dataset_filters
|
dict[str, list[str]]
|
Mapping of facet names to lists of allowed values |
required |
Returns:
| Type | Description |
|---|---|
dict[SourceDatasetType, DataCatalog | DataFrame]
|
Filtered data catalogs |
Source code in packages/climate-ref/src/climate_ref/solver.py
as_frame(catalog)
#
Unwrap a catalog to the DataFrame behind it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
catalog
|
DataFrame | DataCatalog
|
Either a catalog wrapper or a plain frame. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
The catalog as a DataFrame. |
Source code in packages/climate-ref/src/climate_ref/solver.py
catalog_for_requirement(data_catalog, requirement)
#
Resolve the catalog a requirement is solved against, folding in its declared fallbacks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_catalog
|
Mapping[SourceDatasetType, DataFrame | DataCatalog]
|
Data catalogs for each source dataset type. |
required |
requirement
|
DataRequirement
|
The requirement to resolve a catalog for. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame | DataCatalog | None
|
The requirement's catalog, or |
Source code in packages/climate-ref/src/climate_ref/solver.py
check_provider_health(providers, config, filters=None)
#
Health check the providers a solve would use, logging each problem found.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
providers
|
Sequence[DiagnosticProvider]
|
Configured providers |
required |
config
|
Config
|
The application configuration |
required |
filters
|
SolveFilterOptions | None
|
Solve filters, which limit the providers checked to those with a diagnostic the solve would use |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
The problems found for each provider checked, keyed by slug |
Source code in packages/climate-ref/src/climate_ref/solver.py
extract_covered_datasets(data_catalog, requirement)
#
Determine the different diagnostic executions that should be performed with the current data catalog
Source code in packages/climate-ref/src/climate_ref/solver.py
fail_stale_in_progress_executions(db, *, stale_after_seconds=DEFAULT_STALE_EXECUTION_AGE_SECONDS, scratch_root=None, results_root=None)
#
Mark abandoned in-progress executions as failed so the next solve can retry them.
An execution is considered abandoned when it has successful=None and was
created longer ago than stale_after_seconds. This commonly happens when
a worker is killed (OOM, walltime, segfault) before its result-handling
callback ran, or when the join loop crashed mid-flight.
The execution group's dirty flag is set so the existing retry logic
(ExecutionGroup.should_run) picks the work back up on the next solve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
Database
|
The database to inspect |
required |
stale_after_seconds
|
int
|
Minimum age in seconds before an in-progress execution is considered abandoned. Defaults to 6 hours, matching the Celery and LocalExecutor per-task time limits. |
DEFAULT_STALE_EXECUTION_AGE_SECONDS
|
scratch_root
|
Path | None
|
Scratch directory the executions wrote to. When given, an execution with no scratch output is recorded as never started rather than abandoned. |
None
|
results_root
|
Path | None
|
Results directory the API serves logs from.
When given with |
None
|
Returns:
| Type | Description |
|---|---|
int
|
The number of executions that were marked failed. |
Source code in packages/climate-ref/src/climate_ref/solver.py
matches_filter(diagnostic, filters)
#
Check if a diagnostic matches the provided filters
Each filter is optional and a diagnostic will match if it satisfies all the provided filters. i.e. the filters are ANDed together.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
diagnostic
|
Diagnostic
|
Diagnostic to check against the filters |
required |
filters
|
SolveFilterOptions | None
|
Collection of filters to apply to the diagnostic If no filters are provided, the diagnostic is considered to match |
required |
Returns:
| Type | Description |
|---|---|
True if the diagnostic matches the filters, False otherwise
|
|
Source code in packages/climate-ref/src/climate_ref/solver.py
newer_rows(candidate, held)
#
Select the rows of candidate that held lacks, or holds at an older version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
candidate
|
DataFrame
|
Rows offered by a fallback collection. |
required |
held
|
DataFrame
|
Rows already taken, which win a version tie. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
The subset of |
Source code in packages/climate-ref/src/climate_ref/solver.py
solve_executions(data_catalog, diagnostic, provider)
#
Calculate the diagnostic executions that need to be performed for a given diagnostic
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_catalog
|
Mapping[SourceDatasetType, DataFrame | DataCatalog]
|
Data catalogs for each source dataset type |
required |
diagnostic
|
Diagnostic
|
Diagnostic of interest |
required |
provider
|
DiagnosticProvider
|
Provider of the diagnostic |
required |
Returns:
| Type | Description |
|---|---|
Generator[DiagnosticExecution, None, None]
|
A generator that yields the diagnostic executions that need to be performed |
Source code in packages/climate-ref/src/climate_ref/solver.py
solve_required_executions(db, dry_run=False, execute=True, solver=None, config=None, timeout=60, wait=True, one_per_provider=False, one_per_diagnostic=False, filters=None, limit=None, rerun_failed=False, check_health=True)
#
Solve for executions that require recalculation
This may trigger a number of additional calculations depending on what data has been ingested since the last solve.
When wait is True (the default) this blocks until all executions complete,
copying their outputs to the results directory and ingesting them.
timeout bounds that wait; a non-positive timeout (<= 0) waits with no time limit.
wait=False queues the executions and returns immediately without collecting their results.
When check_health is True and the executor runs diagnostics on this host,
each provider in scope is health checked first and an unhealthy provider is left out of the solve.
Returns:
| Type | Description |
|---|---|
SolveOutcome
|
The providers left out as unhealthy and how many submitted executions failed |
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If the executions aren't completed within a positive |
Source code in packages/climate-ref/src/climate_ref/solver.py
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 | |
union_with_fallbacks(primary, fallbacks, primary_type)
#
Fill a reference catalog from its fallback collections.
A dataset held by more than one collection is taken at its newest version, and from the primary when the versions tie. So publishing a dataset to obs4MIPs takes over from the obs4REF copy without any re-ingest, but a newer registry version is not lost to a stale published one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
primary
|
DataFrame | DataCatalog
|
The catalog the requirement asks for, which wins a version tie. |
required |
fallbacks
|
Sequence[DataFrame | DataCatalog]
|
Catalogs that may stand in for it, tried in order. |
required |
primary_type
|
SourceDatasetType
|
Source type the merged rows are delivered under. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame | DataCatalog
|
The primary catalog, extended with the fallback rows that win.
The original catalog is returned untouched when nothing is added.
The added rows carry |