diff --git a/interpret/specification.py b/interpret/specification.py index cd866cd6..95e303f9 100644 --- a/interpret/specification.py +++ b/interpret/specification.py @@ -327,6 +327,7 @@ def create_curvegen(csvv, covariator, regions, farmer='full', specconf=None, get # Clause to set curve baseline extents if configured. clipping_cfg = specconf.get('clipping', False) + clipmodel = None if clipping_cfg: # Validate clipping configuration option. user_assert(clipping_cfg in ['boatpose', 'downdog', True, 'corpsepose', 'plankpose'], @@ -375,7 +376,19 @@ def create_curvegen(csvv, covariator, regions, farmer='full', specconf=None, get curve_global_extrema = curve_extrema(curve) baselineexts = {r: curve_global_extrema for r in regions} - def transform(region, curve): + # instead of accepting exactly one curve, transform can now accept one or more curves + def transform(region, *curves): + # for the special corpsepose/plankpose clipping configurations, we need both current model curve and clip-model curve to perform transform + if clipping_cfg in ['corpsepose', 'plankpose']: + # force there to be exactly two curves in that case: clip_reference_curve and current model curve: curves + user_assert(len(curves) == 2, "corpsepose/plankpose requires both clip-model and target curves") + clip_reference_curve, curve = curves + # for other clipping configurations, we only need the current model curve + else: + user_assert(len(curves) == 1, "expected a single curve for transform") + curve = curves[0] + clip_reference_curve = None + if isinstance(curve, smart_curve.SmartCurve): final_curve = curve else: @@ -420,9 +433,20 @@ def transform(region, curve): direction=ucurve_direction, ) else: # 'corpsepose' or 'plankpose' + # making sure the clip model's (low-risk) curve was passed in to clip against + user_assert( + clip_reference_curve is not None, + "clip-model curve was not provided to clipping transform" + ) + + # if not in right curve format already, convert + if not isinstance(clip_reference_curve, smart_curve.SmartCurve): + clip_reference_curve = smart_curve.CoefficientsCurve( + clip_reference_curve.ccs, weathernames + ) + # Also shift clipmodel down - clip_curve = othermodels[specconf.get('clip-model')].current_curves[region] - clip_curve = smart_curve.ShiftedCurve(clip_curve, -clip_curve.univariate(baselineexts[region])) + clip_curve = smart_curve.ShiftedCurve(clip_reference_curve, -clip_reference_curve.univariate(baselineexts[region])) if clipping_cfg == 'corpsepose': final_curve = smart_curve.MinimumCurve(final_curve, clip_curve) @@ -450,14 +474,27 @@ def transform(region, curve): return final_curve + # By default, transform only receives the current model's curve + transform_curvegens = [curr_curvegen] + + # For corpsepose/plankpose, transform now needs two curves: + # 1) the clip-model reference curve (minlost_lo) + # 2) the current model curve (minlost_hi) + # so clipping does not depend on the cache lookup of the clip-model's curve + if clipping_cfg in ['corpsepose', 'plankpose']: + transform_curvegens = [othermodels[clipmodel].curvegen, curr_curvegen] + + # Pass the prepared curve generator(s) into transform + # In the corpsepose/plankpose case this supplies two curves + # Otherwise it preserves the original single-curve behavior if clipping_cfg and specconf.get('goodmoney', False): - final_curvegen = curvegen.TransformCurveGenerator(transform, "Clipping and Good Money transformation", curr_curvegen) + final_curvegen = curvegen.TransformCurveGenerator(transform, "Clipping and Good Money transformation", *transform_curvegens) elif clipping_cfg: - final_curvegen = curvegen.TransformCurveGenerator(transform, "Clipping transformation", curr_curvegen) + final_curvegen = curvegen.TransformCurveGenerator(transform, "Clipping transformation", *transform_curvegens) elif specconf.get('goodmoney', False): - final_curvegen = curvegen.TransformCurveGenerator(transform, "Good Money transformation", curr_curvegen) + final_curvegen = curvegen.TransformCurveGenerator(transform, "Good Money transformation", *transform_curvegens) else: - final_curvegen = curvegen.TransformCurveGenerator(transform, "Smart curve transformation", curr_curvegen) + final_curvegen = curvegen.TransformCurveGenerator(transform, "Smart curve transformation", *transform_curvegens) final_curvegen.deltamethod_passthrough = True if covariator: @@ -465,6 +502,7 @@ def transform(region, curve): return final_curvegen + def prepare_interp_raw(csvv, weatherbundle, economicmodel, qvals, farmer='full', specconf=None, config=None): """