import copy
import pandas as pd
from logging import debug
import numpy as np
import datetime
import xarray as xr
from ......utils.datastores.empty import init_empty
from ......utils.check.errclass import CifError, CifTypeError
print_all = False
transfor2print = [
# "time_interpolation_std_00006",
# "regrid_std_00008",
# "vertical_interpolation_std_00007",
# "loadfromoutputs_std_00036"
# 'satellites_std_00001',
# 'time_interpolation_std_00012'
# 'dump2inputs_std_00008'
]
# transfor2print = [
# 'dump2inputs_std_00030',
# 'time_interpolation_std_00007',
# 'loadfromoutputs_std_00043'
# ]
# transfor2print = [
# 'fromcontrol_std_00011',
# 'time_interpolation_std_00012',
# 'dump2inputs_std_00006'
# ]
# mode2print = "adj"
mode2print = "fwd"
open_debug = True
[docs]
def aggregate_inout(
transform, ddi,
tmp_inputs, tmp_outputs,
transform_mode, transform_onlyinit,
mapper,
check_transforms=False
):
"""Aggregate inputs and outputs from precursors and successors respectively.
There can be only one precursor per trid, with the possibility of having several sub-dates.
By construction, it is not possible to have several precursors for a given trid to avoid ambiguity.
This could become possible in the future, but it would require further complexity in identifying datastores
(i.e., including precursor and successor in dictionaries, at the cost of reduced readability).
Outputs can have several successors for a given trid. This implies that adjoint sensitivities must be properly
propagated backwards.
Args:
transform (str): The name of the transform currently being processed
ddi (datetime.datetime): The period being processed
tmp_inputs (dict): _description_
tmp_outputs (dict): _description_
transform_mode (str): _description_
transform_onlyinit (bool): _description_
mapper (dict): _description_
check_transforms (bool, optional): _description_. Defaults to False.
Raises:
Exception: _description_
TypeError: _description_
Returns:
_type_: _description_
"""
if transform in transfor2print or print_all:
debug(f'aggreget_inout: origin_in: {transform} / {ddi} / {tmp_inputs}')
if not transform_onlyinit and transform_mode == mode2print and open_debug:
print("AAA")
print(__file__)
import code
code.interact(local=dict(locals(), **globals()))
# Get variables from mapper
transf_mapper = mapper[transform]
precursors = transf_mapper["precursors"]
successors = transf_mapper["successors"]
# Loop on inputs and check precursors
for trid in tmp_inputs:
trid_dict = {}
for di in tmp_inputs[trid]:
nprecursors = len(tmp_inputs[trid][di])
# If no data, continue
if nprecursors == 0:
trid_dict = {di: {}}
continue
# If several precursor raise error.
# Should never happen by construction...
if nprecursors > 1:
raise CifError(
f"Several possible precursors for {trid} for transform {transform}:\n"
f"{precursors[trid].keys()}\n"
"This should not happen. Please check your yaml with a core developer."
)
# Turn input to list if only one precursor to fit expected format
precursor = list(tmp_inputs[trid][di].keys())[0]
# subsimus_in = mapper[precursor]["subsimus"]
subsimus_in = tmp_inputs[trid][di][precursor]
trid_dict[di] = {}
for idate, precursor_di in enumerate(subsimus_in):
# Fill values from precursor
trid_dict[di] = tmp_inputs[trid][di][precursor].get(
precursor_di, {})
# Save input to compute adjoint delta in check_transform mode
if check_transforms and transform_mode == "adj" and not transform_onlyinit:
if "adj_out" in trid_dict[di]:
trid_dict[di]["adj_out_original"] = \
copy.deepcopy(trid_dict[di]["adj_out"])
tmp_inputs[trid] = trid_dict
if transform in transfor2print or print_all:
debug(f'aggreget_inout: target_in: {transform} / {ddi} / {tmp_inputs}')
debug(f'aggreget_inout: origin_out: {transform} / {ddi} / {tmp_outputs}')
if not transform_onlyinit and transform_mode == mode2print and open_debug:
print("BBB")
print(__file__)
import code
code.interact(local=dict(locals(), **globals()))
for trid in tmp_outputs:
for di in tmp_outputs[trid]:
if len(successors[trid]) == 0:
continue
# Check whether there is a need to aggregate several data
aggregated_out = None
to_aggregate = False
for isuccessor, successor in enumerate(successors[trid]):
if successor not in tmp_outputs[trid][di]:
continue
for idate, successor_di in enumerate(tmp_outputs[trid][di][successor]):
# Initialize if None
if aggregated_out is None:
aggregated_out = tmp_outputs[trid][di][successor][successor_di]
# Otherwise means that there is some aggregation to do
else:
to_aggregate = True
# Stop here if no aggregation is needed
if not to_aggregate:
tmp_outputs[trid][di] = aggregated_out if aggregated_out is not None else {}
continue
# Loop over successors and their sub-dates
# If aggregation
aggregated_out = []
is_sparse = None
aggreg_index = 0
for isuccessor, successor in enumerate(successors[trid]):
if successor not in tmp_outputs[trid][di]:
continue
# Check sparse
out_mapper = transf_mapper["outputs"][trid]
if out_mapper.get("sparse_data", False) or out_mapper.get("sampled", False):
if is_sparse is None:
is_sparse = True
elif is_sparse:
raise CifTypeError(
f"The transform {transform} has sparse and non sparse outputs. "
"I cannot deal with that yet..."
)
# Now loop over successor dates
subsimus_out = transf_mapper["subsimus"][ddi]["outputs"][trid]
subsimus_in = mapper[successor]["subsimus"]
for idate, successor_di in enumerate(subsimus_in):
if di not in subsimus_in[successor_di]['inputs'].get(trid, []):
continue
aggreg_index += 1
# Fetch data to aggregate and turn to empty datastore if sparse
if successor_di not in tmp_outputs[trid][di][successor]:
debug(
f"No value in {successor_di} for {transform} / {successor} / {di}"
)
dict_to_aggregate = tmp_outputs[trid][di][successor].get(
successor_di,
init_empty() if is_sparse else {}
)
if is_sparse:
if len(dict_to_aggregate) == 0:
dict_to_aggregate = init_empty()
# Aggregate data
aggregated_out.append(dict_to_aggregate)
# Deal differently sparse and non sparse data
if is_sparse:
if isinstance(aggregated_out[-1], pd.DataFrame):
aggregated_out[-1][
("metadata", "itransform")] = isuccessor
aggregated_out[-1][
("metadata", "idate")] = idate
if transform in transfor2print or print_all:
debug(
f"{transform} / {di} / {successor} / {successor_di} / {idate} / {len(aggregated_out[-1])}")
# Now aggregate sparse or sum arrays
if is_sparse:
tmp_outputs[trid][di] = pd.concat(aggregated_out)
# Stores idates and issuccessors in an unalterable place
if 'aggregation_index' not in transf_mapper:
transf_mapper['aggregation_index'] = {}
if trid not in transf_mapper['aggregation_index']:
transf_mapper['aggregation_index'][trid] = {}
if ddi not in transf_mapper['aggregation_index'][trid]:
transf_mapper['aggregation_index'][trid][ddi] = {}
if transform_mode == 'adj':
transf_mapper['aggregation_index'][trid][ddi][di] = {
"idate": copy.deepcopy(
tmp_outputs[trid][di][("metadata", "idate")].values
),
"itransform": copy.deepcopy(
tmp_outputs[trid][di][(
"metadata", "itransform")].values
)
}
else:
if np.all([d == {} for d in aggregated_out]):
tmp_outputs[trid][di] = {}
continue
to_sum = 0
other_outputs = {}
for a in aggregated_out:
# Update other_outputs by attributes other than adj_out
for attr in a:
if attr == "adj_out":
to_sum += a["adj_out"]
else:
other_outputs[attr] = a[attr]
tmp_outputs[trid][di] = {
"adj_out": to_sum,
"original_outputs": copy.deepcopy(aggregated_out),
**other_outputs
}
if transform in transfor2print or print_all:
debug(f'aggreget_inout: target_out: {transform} / {ddi} / {tmp_outputs}')
if 'aggregation_index' not in transf_mapper:
debug(f'aggregation_index not in transf_mapper for {transform}')
elif trid not in transf_mapper['aggregation_index']:
debug(f'{trid} not in aggregation_index for {transform}')
elif ddi not in transf_mapper['aggregation_index'][trid]:
debug(f'{ddi} not in aggregation_index for {transform} / {trid}')
else:
for trid in tmp_outputs:
for di in tmp_outputs[trid]:
if di not in transf_mapper['aggregation_index'][trid][ddi]:
debug(f'{di} not in transf_mapper for {transform} / {trid} / {ddi}')
continue
debug(
f"aggregation index: idate {transf_mapper['aggregation_index'][trid][ddi][di]['idate'].shape}")
debug(
f"aggregation index: itransform {transf_mapper['aggregation_index'][trid][ddi][di]['itransform'].shape}")
return tmp_inputs, tmp_outputs
[docs]
def deaggregate_inout(
transform, transform_mode,
transform_onlyinit,
ddi,
tmp_datastore,
mapper,
check_transforms=False
):
# Get variables from mapper
transf_mapper = mapper[transform]
precursors = transf_mapper["precursors"]
successors = transf_mapper["successors"]
# Deal with inputs
tmp_inputs = tmp_datastore["inputs"]
if transform in transfor2print or print_all:
debug(f'deaggreget_inout: origin_in: {transform} / {ddi} / {tmp_inputs}')
for trid in transf_mapper["subsimus"][ddi]["inputs"]:
trid_dict = {}
trid_inputs = tmp_inputs.get(trid, {})
# for di in transf_mapper["subsimus"][ddi]["inputs"][trid]:
for di in trid_inputs:
di_inputs = trid_inputs[di]
di_dict = {}
for precursor in precursors.get(trid, []):
precursor_dict = {}
subsimus_in = mapper[precursor]["subsimus"]
for precursor_di in subsimus_in:
# Skip if subsimu not in precursor subsimulations
if di not in subsimus_in[precursor_di]['outputs'].get(trid, []):
continue
precursor_dict[precursor_di] = di_inputs
# if type(di_inputs) == pd.core.frame.DataFrame:
# precursor_dict[precursor_di] = di_inputs
# else:
# precursor_dict[precursor_di] = di_inputs.get(
# precursor, di_inputs)
# Now compute delta_adj_out for check_transforms
if check_transforms:
if "adj_out_original" in di_inputs:
precursor_dict[precursor_di]["delta_adj_out"] = (
di_inputs["adj_out"] -
di_inputs["adj_out_original"]
)
del di_inputs["adj_out_original"]
di_dict[precursor] = precursor_dict
trid_dict[di] = di_dict
tmp_inputs[trid] = trid_dict
if transform in transfor2print or print_all:
debug(f'deaggreget_inout: target_in: {transform} / {ddi} / {tmp_inputs}')
# Deal with outputs
tmp_outputs = tmp_datastore["outputs"]
if transform in transfor2print or print_all:
debug(f'deaggreget_inout: origin_out: {transform} / {ddi} / {tmp_outputs}')
if not transform_onlyinit and transform_mode == mode2print and open_debug:
print("BBB")
print(__file__)
import code
code.interact(local=dict(locals(), **globals()))
for trid in transf_mapper["subsimus"][ddi]["outputs"]:
trid_dict = {}
trid_outputs = tmp_outputs.get(trid, {})
subsimus_out = transf_mapper["subsimus"][ddi]["outputs"][trid]
trid_successors = successors.get(trid, [])
aggreg_index = 0
for di in subsimus_out:
di_dict = {}
# Skip if no outputs for this sub-simulation
if di not in trid_outputs:
trid_dict[di] = di_dict
continue
di_outputs = trid_outputs[di]
for isuccessor, successor in enumerate(trid_successors):
successor_dict = {}
subsimus_in = mapper[successor]["subsimus"]
for idate, successor_di in enumerate(subsimus_in):
# Skip if subsimu not in successor subsimulations
if di not in subsimus_in[successor_di]['inputs'].get(trid, []):
continue
aggreg_index += 1
successor_di_dict = {}
# Dataframe processing
if type(di_outputs) == pd.core.frame.DataFrame:
# Use information from aggregation index if any
ref_idates = transf_mapper.get('aggregation_index', {}).get(trid, {}).get(
ddi, {}).get(di, {}).get("idate", idate * np.ones(len(di_outputs)))
ref_itransforms = transf_mapper.get('aggregation_index', {}).get(trid, {}).get(
ddi, {}).get(di, {}).get("itransform", isuccessor * np.ones(len(di_outputs)))
mask = (ref_itransforms == isuccessor) & (
ref_idates == idate)
successor_di_dict = di_outputs.loc[mask]
# Array processing
else:
if successor in di_outputs:
raise CifError("Does this ever happen?")
successor_di_dict = {
v: di_outputs[v] for v in di_outputs
if v != "original_outputs"
}
# Replacing original adj_out before aggregation
# If available
if "adj_out" in successor_di_dict \
and "original_outputs" in di_outputs:
original_outputs = di_outputs["original_outputs"][aggreg_index - 1]
if "adj_out" in original_outputs:
successor_di_dict["adj_out"] = copy.deepcopy(
original_outputs["adj_out"])
else:
successor_di_dict["adj_out"] = \
0. * successor_di_dict["adj_out"]
successor_dict[successor_di] = successor_di_dict
di_dict[successor] = successor_dict
trid_dict[di] = di_dict
tmp_outputs[trid] = trid_dict
if transform in transfor2print or print_all:
debug(f'deaggreget_inout: target_out: {transform} / {ddi} / {tmp_outputs}')
return tmp_inputs, tmp_outputs