From 80762ecf41e14efc5d3ff9847b1efcddd54e621d Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 3 Oct 2023 21:27:46 -0700 Subject: [PATCH 01/41] Estimators for diff-in-diff, synthetic control and synthetic diff-in-diff --- .../ml/causal/BaseDiffInDiffEstimator.scala | 52 ++++ .../azure/synapse/ml/causal/CacheOps.scala | 18 ++ .../ml/causal/DiffInDiffEstimator.scala | 33 +++ .../ml/causal/DiffInDiffEstimatorParams.scala | 8 + .../synapse/ml/causal/DoubleMLParams.scala | 36 +-- .../synapse/ml/causal/SharedParams.scala | 53 ++++ .../ml/causal/SyntheticControlEstimator.scala | 82 +++++++ .../causal/SyntheticDiffInDiffEstimator.scala | 97 ++++++++ .../ml/causal/SyntheticEstimator.scala | 227 ++++++++++++++++++ .../ml/causal/SyntheticEstimatorParams.scala | 43 ++++ .../synapse/ml/causal/linalg/MatrixOps.scala | 142 +++++++++++ .../synapse/ml/causal/linalg/VectorOps.scala | 179 ++++++++++++++ .../synapse/ml/causal/linalg/package.scala | 8 + .../causal/opt/ConstrainedLeastSquare.scala | 79 ++++++ .../synapse/ml/causal/opt/MirrorDescent.scala | 136 +++++++++++ 15 files changed, 1163 insertions(+), 30 deletions(-) create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala create mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala new file mode 100644 index 0000000000..57808be98e --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -0,0 +1,52 @@ +package com.microsoft.azure.synapse.ml.causal + +import com.microsoft.azure.synapse.ml.causal.linalg.DVector +import org.apache.spark.ml.feature.VectorAssembler +import org.apache.spark.ml.param.ParamMap +import org.apache.spark.ml.regression.LinearRegression +import org.apache.spark.ml.{Estimator, Model} +import org.apache.spark.sql.functions.col +import org.apache.spark.sql.types.{LongType, StructField, StructType} +import org.apache.spark.sql.{DataFrame, Dataset, Row} + +abstract class BaseDiffInDiffEstimator(override val uid: String) + extends Estimator[DiffInDiffModel] + with DiffInDiffEstimatorParams { + + override def transformSchema(schema: StructType): StructType = throw new NotImplementedError + + override def copy(extra: ParamMap): Estimator[DiffInDiffModel] = defaultCopy(extra) + + val interactionCol = "interaction" + + private[causal] def fitLinearModel(did_data: DataFrame, weightCol: Option[String] = None) = { + val assembler = new VectorAssembler() + .setInputCols(Array(getPostTreatmentCol, getTreatmentCol, interactionCol)) + .setOutputCol("features") + + val regression = weightCol + .map(new LinearRegression().setWeightCol) + .getOrElse(new LinearRegression()) + + regression + .setFeaturesCol("features") + .setLabelCol(getOutcomeCol) + .setFitIntercept(true) + .setLoss("squaredError") + .setRegParam(0.0) + + assembler.transform _ andThen regression.fit apply did_data + } +} + +case class DiffInDiffSummary(treatmentEffect: Double, standardError: Double, + timeWeights: Option[DVector] = None, timeIntercept: Option[Double] = None, + unitWeights: Option[DVector] = None, unitIntercept: Option[Double] = None) + +class DiffInDiffModel(override val uid: String, val summary: DiffInDiffSummary) extends Model[DiffInDiffModel] { + override def copy(extra: ParamMap): DiffInDiffModel = defaultCopy(extra) + + override def transform(dataset: Dataset[_]): DataFrame = throw new NotImplementedError + + override def transformSchema(schema: StructType): StructType = throw new NotImplementedError +} \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala new file mode 100644 index 0000000000..662689860a --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala @@ -0,0 +1,18 @@ +package com.microsoft.azure.synapse.ml.causal + +import breeze.linalg.{DenseVector => BDV} +import com.microsoft.azure.synapse.ml.causal.linalg.DVector +trait CacheOps[T] { + def checkpoint(data: T): T = data + def cache(data: T): T = data +} + +object BDVCacheOps extends CacheOps[BDV[Double]] { + override def checkpoint(data: BDV[Double]): BDV[Double] = data + override def cache(data: BDV[Double]): BDV[Double] = data +} + +object DVectorCacheOps extends CacheOps[DVector] { + override def checkpoint(data: DVector): DVector = data.localCheckpoint(true) + override def cache(data: DVector): DVector = data.cache +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala new file mode 100644 index 0000000000..3c2d82b82a --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -0,0 +1,33 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql._ +import org.apache.spark.sql.functions.col +import org.apache.spark.sql.types._ + +class DiffInDiffEstimator(override val uid: String) + extends BaseDiffInDiffEstimator(uid) { + + def this() = this(Identifiable.randomUID("did")) + + override def fit(dataset: Dataset[_]): DiffInDiffModel = { + val postTreatment = col(getPostTreatmentCol) + val treatment = col(getTreatmentCol) + val outcome = col(getOutcomeCol) + + val didData = dataset.select( + postTreatment.cast(IntegerType).as(getPostTreatmentCol), + treatment.cast(IntegerType).as(getTreatmentCol), + outcome.cast(DoubleType).as(getOutcomeCol) + ) + .withColumn(interactionCol, treatment * postTreatment) + + val linearModel = fitLinearModel(didData) + + val treatmentEffect = linearModel.coefficients(2) + val standardError = linearModel.summary.coefficientStandardErrors(2) + val summary = DiffInDiffSummary(treatmentEffect, standardError) + val model = new DiffInDiffModel(this.uid, summary).setParent(this) + copyValues(model) + } +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala new file mode 100644 index 0000000000..5756fdf061 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala @@ -0,0 +1,8 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.ml.param.Params + +trait DiffInDiffEstimatorParams extends Params + with HasTreatmentCol + with HasOutcomeCol + with HasPostTreatmentCol \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DoubleMLParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DoubleMLParams.scala index a65ed47980..933da1a84a 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DoubleMLParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DoubleMLParams.scala @@ -3,38 +3,14 @@ package com.microsoft.azure.synapse.ml.causal -import com.microsoft.azure.synapse.ml.core.contracts.{HasFeaturesCol, HasLabelCol, HasWeightCol} +import com.microsoft.azure.synapse.ml.core.contracts.{HasFeaturesCol, HasWeightCol} import com.microsoft.azure.synapse.ml.param.EstimatorParam -import org.apache.spark.ml.classification.{LogisticRegression, ProbabilisticClassifier} -import org.apache.spark.ml.{Estimator, Model} import org.apache.spark.ml.ParamInjections.HasParallelismInjected -import org.apache.spark.ml.param.shared.{HasMaxIter, HasPredictionCol} -import org.apache.spark.ml.param.{DoubleArrayParam, DoubleParam, Param, Params} +import org.apache.spark.ml.classification.{LogisticRegression, ProbabilisticClassifier} +import org.apache.spark.ml.param.shared.HasMaxIter +import org.apache.spark.ml.param.{DoubleArrayParam, DoubleParam, Params} import org.apache.spark.ml.regression.Regressor - -trait HasTreatmentCol extends Params { - val treatmentCol = new Param[String](this, "treatmentCol", "treatment column") - def getTreatmentCol: String = $(treatmentCol) - - /** - * Set name of the column which will be used as treatment - * - * @group setParam - */ - def setTreatmentCol(value: String): this.type = set(treatmentCol, value) -} - -trait HasOutcomeCol extends Params { - val outcomeCol: Param[String] = new Param[String](this, "outcomeCol", "outcome column") - def getOutcomeCol: String = $(outcomeCol) - - /** - * Set name of the column which will be used as outcome - * - * @group setParam - */ - def setOutcomeCol(value: String): this.type = set(outcomeCol, value) -} +import org.apache.spark.ml.{Estimator, Model} trait DoubleMLParams extends Params with HasTreatmentCol with HasOutcomeCol with HasFeaturesCol @@ -86,7 +62,7 @@ trait DoubleMLParams extends Params def setSampleSplitRatio(value: Array[Double]): this.type = set(sampleSplitRatio, value) private[causal] object DoubleMLModelTypes extends Enumeration { - type TreatmentType = Value + type DoubleMLModelTypes = Value val Binary, Continuous = Value } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala new file mode 100644 index 0000000000..8baff45c40 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala @@ -0,0 +1,53 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.ml.param.{Param, Params} + +trait HasTreatmentCol extends Params { + val treatmentCol = new Param[String](this, "treatmentCol", "treatment column") + def getTreatmentCol: String = $(treatmentCol) + + /** + * Set name of the column which will be used as treatment + * + * @group setParam + */ + def setTreatmentCol(value: String): this.type = set(treatmentCol, value) +} + +trait HasOutcomeCol extends Params { + val outcomeCol: Param[String] = new Param[String](this, "outcomeCol", "outcome column") + def getOutcomeCol: String = $(outcomeCol) + + /** + * Set name of the column which will be used as outcome + * + * @group setParam + */ + def setOutcomeCol(value: String): this.type = set(outcomeCol, value) +} + +trait HasPostTreatmentCol extends Params { + final val postTreatmentCol = new Param[String](this, "postTreatmentCol", "post treatment indicator column") + def getPostTreatmentCol: String = $(postTreatmentCol) + + /** + * Set name of the column which tells whether the outcome is measured post treatment. + * + * @group setParam + */ + def setPostTreatmentCol(value: String): this.type = set(postTreatmentCol, value) +} + +trait HasUnitCol extends Params { + final val unitCol = new Param[String](this, "unitCol", + "Column that identifies the units in panel data") + def getUnitCol: String = $(unitCol) + def setUnitCol(value: String): this.type = set(unitCol, value) +} + +trait HasTimeCol extends Params { + final val timeCol = new Param[String](this, "timeCol", + "Column that identifies the time when outcome is measured in panel data") + def getTimeCol: String = $(timeCol) + def setTimeCol(value: String): this.type = set(timeCol, value) +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala new file mode 100644 index 0000000000..174078abb1 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -0,0 +1,82 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.functions._ +import org.apache.spark.sql.types.IntegerType +import org.apache.spark.sql.{Dataset, Row} + +class SyntheticControlEstimator(override val uid: String) + extends BaseDiffInDiffEstimator(uid) + with SyntheticEstimator + with DiffInDiffEstimatorParams + with SyntheticEstimatorParams { + + import SyntheticEstimator._ + + def this() = this(Identifiable.randomUID("sc")) + + override def fit(dataset: Dataset[_]): DiffInDiffModel = { + val df = dataset.toDF + val controlDf = df.filter(not(treatment)).cache + val preDf = df.filter(not(postTreatment)).cache + val controlPreDf = controlDf.filter(not(postTreatment)).cache + val timeIdx = createIndex(controlPreDf, getTimeCol, TimeIdxCol).cache + val unitIdx = createIndex(controlPreDf, getUnitCol, UnitIdxCol).cache + + // indexing + val indexedPreDf = preDf.join(timeIdx, preDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") + .join(unitIdx, preDf(getUnitCol) === unitIdx(getUnitCol), "left_outer") + .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) + .localCheckpoint(true) + + val (unitWeights, unitIntercept) = fitUnitWeights( + handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), + zeta = 0d, + fitIntercept = false, + seed = 47 + ) + + // join weights + val Row(_: Long, u: Long) = df.agg( + countDistinct(when(postTreatment, col(getTimeCol))), + countDistinct(when(treatment, col(getUnitCol))), + ).head + + val indexedDf = df.join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") + + val didData = indexedDf.select( + col(UnitIdxCol), + postTreatment.cast(IntegerType).as(getPostTreatmentCol), + treatment.cast(IntegerType).as(getTreatmentCol), + outcome + ).as("l") + .join(unitWeights.as("u"), col(s"l.$UnitIdxCol") === col("u.i"), "left_outer") + .select( + postTreatment, + treatment, + outcome, + ( + coalesce(col("u.value"), lit(1d / u)) + // unit weights + lit(epsilon) // avoid zero weights + ).as(weightsCol), + (treatment * postTreatment).as(interactionCol) + ) + + val linearModel = fitLinearModel(didData, Some(weightsCol)) + + val treatmentEffect = linearModel.coefficients(2) + val standardError = linearModel.summary.coefficientStandardErrors(2) + + val summary = DiffInDiffSummary( + treatmentEffect, + standardError, + None, + None, + Some(unitWeights), + Some(unitIntercept) + ) + + val model = new DiffInDiffModel(this.uid, summary).setParent(this) + copyValues(model) + } +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala new file mode 100644 index 0000000000..d55acbd8fb --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -0,0 +1,97 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.functions._ +import org.apache.spark.sql.types.IntegerType +import org.apache.spark.sql.{Dataset, Row} + +class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInDiffEstimator(uid) + with SyntheticEstimator + with SyntheticEstimatorParams { + + import SyntheticEstimator._ + + def this() = this(Identifiable.randomUID("sdid")) + + override def fit(dataset: Dataset[_]): DiffInDiffModel = { + val df = dataset.toDF + val controlDf = df.filter(not(treatment)).cache + val preDf = df.filter(not(postTreatment)).cache + val controlPreDf = controlDf.filter(not(postTreatment)).cache + val timeIdx = createIndex(controlPreDf, getTimeCol, TimeIdxCol).cache + val unitIdx = createIndex(controlPreDf, getUnitCol, UnitIdxCol).cache + + // indexing + val indexedControlDf = controlDf.join(timeIdx, controlDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") + .join(unitIdx, controlDf(getUnitCol) === unitIdx(getUnitCol), "left_outer") + .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) + .localCheckpoint(true) + + val indexedPreDf = preDf.join(timeIdx, preDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") + .join(unitIdx, preDf(getUnitCol) === unitIdx(getUnitCol), "left_outer") + .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) + .localCheckpoint(true) + + // fit time weights + val (timeWeights, timeIntercept) = fitTimeWeights( + handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), + seed = 47 + ) + + // fit unit weights + val zeta = calculateRegularization(df) + val (unitWeights, unitIntercept) = fitUnitWeights( + handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), + zeta, + fitIntercept = true, + seed = 47 + ) + + // join weights + val Row(t: Long, u: Long) = df.agg( + countDistinct(when(postTreatment, col(getTimeCol))), + countDistinct(when(treatment, col(getUnitCol))), + ).head + + val indexed_df = df.join(timeIdx, df(getTimeCol) === timeIdx(getTimeCol), "left_outer") + .join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") + + val didData = indexed_df.select( + col(UnitIdxCol), + col(TimeIdxCol), + postTreatment.cast(IntegerType).as(getPostTreatmentCol), + treatment.cast(IntegerType).as(getTreatmentCol), + outcome + ).as("l") + .join(timeWeights.as("t"), col(s"l.$TimeIdxCol") === col("t.i"), "left_outer") + .join(unitWeights.as("u"), col(s"l.$UnitIdxCol") === col("u.i"), "left_outer") + .select( + postTreatment, + treatment, + outcome, + ( + coalesce(col("t.value"), lit(1d / t)) * // time weights + coalesce(col("u.value"), lit(1d / u)) + // unit weights + lit(epsilon) // avoid zero weights + ).as(weightsCol), + (treatment * postTreatment).as(interactionCol) + ) + + val linearModel = fitLinearModel(didData, Some(weightsCol)) + + val treatmentEffect = linearModel.coefficients(2) + val standardError = linearModel.summary.coefficientStandardErrors(2) + + val summary = DiffInDiffSummary( + treatmentEffect, + standardError, + Some(timeWeights), + Some(timeIntercept), + Some(unitWeights), + Some(unitIntercept) + ) + + val model = new DiffInDiffModel(this.uid, summary).setParent(this) + copyValues(model) + } +} \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala new file mode 100644 index 0000000000..6aa2b991c4 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -0,0 +1,227 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.sql.expressions.{UserDefinedFunction, Window} +import org.apache.spark.sql.functions._ +import org.apache.spark.sql.{DataFrame, Encoder, Encoders, Row} +import org.apache.spark.sql.types._ +import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} +import com.microsoft.azure.synapse.ml.causal.linalg._ +import com.microsoft.azure.synapse.ml.causal.opt.ConstrainedLeastSquare +import scala.util.Random + +trait SyntheticEstimator { + this: DiffInDiffEstimatorParams with SyntheticEstimatorParams => + + import SyntheticEstimator._ + + implicit val vectorOps: DVectorOps.type = DVectorOps + implicit val matrixOps: DMatrixOps.type = DMatrixOps + implicit val matrixEntryEncoder: Encoder[MatrixEntry] = Encoders.product[MatrixEntry] + implicit val vectorEntryEncoder: Encoder[VectorEntry] = Encoders.product[VectorEntry] + + private[causal] lazy val postTreatment = col(getPostTreatmentCol) + private[causal] lazy val treatment = col(getTreatmentCol) + private[causal] lazy val outcome = col(getOutcomeCol) + private[causal] val interactionCol = "interaction" + private[causal] val weightsCol = "weights" + private[causal] val epsilon = 1E-10 + + private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, seed: Long): (DVector, Double) = { + val size = matrixOps.size(A) + if (size._1 * size._2 <= 1000000) { + // If matrix size is less than 1M, collect the data on the driver node and solve it locally, where matrix-vector + // multiplication is done with breeze. It's much faster than solving with Spark at scale. + implicit val bzMatrixOps: MatrixOps[BDM[Double], BDV[Double]] = BzMatrixOps + implicit val bzVectorOps: VectorOps[BDV[Double]] = BzVectorOps + implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps + + val bzA = convertToBDM(A.collect()) + val bzb = convertToBDV(b.collect()) + val solver = new ConstrainedLeastSquare[BDM[Double], BDV[Double]]( + step = this.getStepSize, maxIter = this.getMaxIter, + numIterNoChange = getNumIterNoChange, tol = this.getTol + ) + + val (x, intercept) = solver.solve(bzA, bzb, lambda, fitIntercept, seed) + val xdf = A.sparkSession.createDataset[VectorEntry](x.mapPairs((i, v) => VectorEntry(i, v)).toArray.toSeq) + (xdf, intercept) + } else { + implicit val cacheOps: CacheOps[DVector] = DVectorCacheOps + val solver = new ConstrainedLeastSquare[DMatrix, DVector]( + step = this.getStepSize, maxIter = this.getMaxIter, + numIterNoChange = getNumIterNoChange, tol = this.getTol + ) + + solver.solve(A, b, lambda, fitIntercept, seed) + } + } + + private[causal] def fitTimeWeights(indexedControlDf: DataFrame, seed: Long = Random.nextLong): (DVector, Double) = { + val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache + + val outcomePre = indexedPreControl + .select( + col(UnitIdxCol).as("i"), + col(TimeIdxCol).as("j"), + col(getOutcomeCol).as("value") + ) + .as[MatrixEntry] + + val outcomePostMean = indexedControlDf.filter(postTreatment) + .groupBy(col(UnitIdxCol).as("i")) + .agg(avg(col(getOutcomeCol)).as("value")) + .as[VectorEntry] + + solveCLS(outcomePre, outcomePostMean, lambda = 0d, fitIntercept = true, seed) + } + + private[causal] def calculateRegularization(data: DataFrame): Double = { + val Row(firstDiffStd: Double) = data + .filter(not(treatment) and not(postTreatment)) + .select( + (outcome - + lag(outcome, 1).over( + Window.partitionBy(col(getUnitCol)).orderBy(col(getTimeCol)) + )).as("diff") + ) + .agg(stddev_samp(col("diff"))) + .head + + val nTreatedPost = data.filter(treatment and postTreatment).count + val zeta = math.pow(nTreatedPost, 0.25) * firstDiffStd + zeta + } + + private[causal] def fitUnitWeights(indexedPreDf: DataFrame, + zeta: Double, + fitIntercept: Boolean, + seed: Long = Random.nextLong): (DVector, Double) = { + + + val outcomePreControl = indexedPreDf.filter(not(treatment)) + .select(col(TimeIdxCol).as("i"), col(UnitIdxCol).as("j"), outcome.as("value")) + .as[MatrixEntry] + + val outcomePreTreatMean = indexedPreDf.filter(treatment) + .groupBy(col(TimeIdxCol).as("i")) + .agg(avg(outcome).as("value")) + .as[VectorEntry] + + val lambda = if (zeta == 0) 0d else { + val t_pre = matrixOps.size(outcomePreControl)._1 // # of time periods pre-treatment + zeta * zeta * t_pre + } + + val (weights, intercept) = solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept, seed) + (weights, intercept) + } + + private[causal] def handleMissingOutcomes(indexed: DataFrame, maxTimeLength: Int): DataFrame = { + // "skip", "zero", "impute" + getHandleMissingOutcome match { + case "skip" => + indexed.withColumn("time_count", count(col(TimeIdxCol)).over(Window.partitionBy(col(UnitIdxCol)))) + // Only skip units from the control_pre group where there is missing data. + .filter(col("time_count") === lit(maxTimeLength) or treatment or postTreatment) + .drop("time_count") + case "zero" => + indexed + case "impute" => + // Only impute the control_pre group. + val controlPre = indexed.filter(not(treatment) and not(postTreatment)) + + val imputed = imputeTimeSeries(controlPre, maxTimeLength) + .withColumn(getTreatmentCol, lit(false)) + .withColumn(getPostTreatmentCol, lit(false)) + + indexed.as("l").join( + imputed.as("r"), + col(s"l.$UnitIdxCol") === col(s"r.$UnitIdxCol") and col(s"l.$TimeIdxCol") === col(s"r.$TimeIdxCol"), + "full_outer" + ).select( + coalesce(col(s"l.$UnitIdxCol"), col(s"r.$UnitIdxCol")).as(UnitIdxCol), + coalesce(col(s"l.$TimeIdxCol"), col(s"r.$TimeIdxCol")).as(TimeIdxCol), + coalesce(col(s"l.$getOutcomeCol"), col(s"r.$getOutcomeCol")).as(getOutcomeCol), + coalesce(col(s"l.$getTreatmentCol"), col(s"r.$getTreatmentCol")).as(getTreatmentCol), + coalesce(col(s"l.$getPostTreatmentCol"), col(s"r.$getPostTreatmentCol")).as(getPostTreatmentCol) + ) + } + } + + private def imputeTimeSeries(df: DataFrame, maxTimeLength: Int): DataFrame = { + val impute: UserDefinedFunction = udf((timeIndex: Seq[Int], values: Seq[Double]) => { + val valueMap = timeIndex.zip(values).toMap + val range = 0 until maxTimeLength + range.map { i => valueMap.getOrElse(i, imputeMissingValue(i, valueMap, range)) } + }) + + df.groupBy(UnitIdxCol).agg( + collect_list(TimeIdxCol).as(TimeIdxCol), collect_list(outcome).as(getOutcomeCol) + ) + .select(col(UnitIdxCol), explode(impute(col(TimeIdxCol), outcome)).as("exploded")) + .select( + col(UnitIdxCol), + col("exploded._1").as(TimeIdxCol), + col("exploded._2").as(getOutcomeCol) + ) + } +} + +object SyntheticEstimator { + val UnitIdxCol = "Unit_idx" + val TimeIdxCol = "Time_idx" + + private def imputeMissingValue(i: Int, valueMap: Map[Int, Double], range: Range): (Int, Double) = { + // Find the nearest neighbors using collectFirst + def findNeighbor(direction: Int): Option[Double] = { + val searchRange = if (direction == -1) range.reverse else range + searchRange.collectFirst { + case j if j * direction > i * direction && valueMap.contains(j) => valueMap(j) + } + } + + (findNeighbor(-1), findNeighbor(1)) match { + case (Some(left), Some(right)) => (i, (left + right) / 2.0) + case (Some(left), None) => (i, left) + case (None, Some(right)) => (i, right) + case (None, None) => (i, 0.0) // Should never happen + } + } + + private def convertToBDM(mat: Array[MatrixEntry]): BDM[Double] = { + val numRows = mat.map(_.i).max.toInt + 1 + val numCols = mat.map(_.j).max.toInt + 1 + val denseMatrix = BDM.zeros[Double](numRows, numCols) + mat.foreach(entry => { + denseMatrix(entry.i.toInt, entry.j.toInt) = entry.value + }) + + denseMatrix + } + + private def convertToBDV(vec: Array[VectorEntry]): BDV[Double] = { + val length = vec.map(_.i).max.toInt + 1 + val denseVector = BDV.zeros[Double](length) + + vec.foreach(entry => { + denseVector(entry.i.toInt) = entry.value + }) + + denseVector + } + + private def assignRowIndex(df: DataFrame, colName: String): DataFrame = { + df.sqlContext.createDataFrame( + df.rdd.zipWithIndex.map(element => + Row.fromSeq(Seq(element._2) ++ element._1.toSeq) + ), + StructType( + Array(StructField(colName, LongType, nullable = false)) ++ df.schema.fields + ) + ) + } + + private[causal] def createIndex(data: DataFrame, inputCol: String, indexCol: String): DataFrame = { + assignRowIndex(data.select(col(inputCol)).distinct.orderBy(col(inputCol)), indexCol) + } +} \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala new file mode 100644 index 0000000000..dd4ea5b344 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala @@ -0,0 +1,43 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.ml.param.{IntParam, Param, ParamValidators, Params} +import org.apache.spark.ml.param.shared.{HasMaxIter, HasStepSize, HasTol} + +trait SyntheticEstimatorParams extends Params + with HasUnitCol + with HasTimeCol + with HasMaxIter + with HasStepSize + with HasTol { + + protected val supportedMissingOutcomes: Array[String] = Array("skip", "zero", "impute") + final val handleMissingOutcome = new Param[String](this, "handleMissingOutcome", + "How to handle missing outcomes. Options are skip (which will filter out units with missing outcomes), " + + "zero (fill in missing outcomes with zero), or impute (impute with nearest available outcomes, " + + "or mean if two nearest outcomes are available)", + ParamValidators.inArray(supportedMissingOutcomes)) + + def getHandleMissingOutcome: String = $(handleMissingOutcome) + + def setHandleMissingOutcome(value: String): this.type = set(handleMissingOutcome, value) + + final val numIterNoChange = new IntParam(this, "numIterNoChange", + "Early termination when number of iterations without change reached.", ParamValidators.gt(0)) + + def getNumIterNoChange: Option[Int] = get(numIterNoChange) + + def setNumIterNoChange(value: Int): this.type = set(numIterNoChange, value) + + def setMaxIter(value: Int): this.type = set(maxIter, value) + + def setStepSize(value: Double): this.type = set(stepSize, value) + + def setTol(value: Double): this.type = set(tol, value) + + setDefault( + stepSize -> 0.5, + tol -> 1E-3, + maxIter -> 100, + handleMissingOutcome -> "zero" + ) +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala new file mode 100644 index 0000000000..b9227b5415 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala @@ -0,0 +1,142 @@ +package com.microsoft.azure.synapse.ml.causal.linalg + +import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} +import breeze.stats.mean +import org.apache.spark.sql.functions.{col, lit, sum} +import org.apache.spark.sql.types.LongType +import org.apache.spark.sql.{Encoder, Encoders, Row} + +case class MatrixEntry(i: Long, j: Long, value: Double) + +trait MatrixOps[TMatrix, TVector] { + /** + * alpha*A*x + beta*y + */ + def gemv(A: TMatrix, + x: TVector, + yOpt: Option[TVector] = None, + alpha: Double = 1.0, + beta: Double = 1.0, + aTranspose: Boolean = false): TVector + + def transpose(matrix: TMatrix): TMatrix + + def centerColumns(matrix: TMatrix): TMatrix + + /** + * Computes the mean of each column. The result is transposed into a vector. + */ + def colMean(matrix: TMatrix): TVector + + def size(matrix: TMatrix): (Long, Long) +} + +object DMatrixOps extends MatrixOps[DMatrix, DVector] { + private implicit val MatrixEntryEncoder: Encoder[MatrixEntry] = Encoders.product[MatrixEntry] + private implicit val VectorEntryEncoder: Encoder[VectorEntry] = Encoders.product[VectorEntry] + + /** + * alpha*A*x + beta*y + */ + def gemv(A: DMatrix, + x: DVector, + yOpt: Option[DVector] = None, + alpha: Double = 1.0, + beta: Double = 1.0, + aTranspose: Boolean = false): DVector = { + + val alphaAx = (if (aTranspose) transpose(A) else A).as("l") + .join(x.as("r"), col("l.j") === col("r.i"), "inner") + .groupBy(col("l.i")) + .agg(sum(lit(alpha) * col("l.value") * col("r.value")).as("value")) + .as[VectorEntry] + + yOpt.map { + y => + DVectorOps.axpy(y, Some(alphaAx), beta) + }.getOrElse(alphaAx) + .as[VectorEntry] + } + + def transpose(matrix: DMatrix): DMatrix = { + matrix.select( + col("j").as("i"), + col("i").as("j"), + col("value") + ).as[MatrixEntry] + } + + override def centerColumns(matrix: DMatrix): DMatrix = { + val colMean = matrix.groupBy(col("j")).agg( + org.apache.spark.sql.functions.mean(col("value")).as("value") + ) + + matrix.as("l").join(colMean.as("r"), col("l.j") === col("r.j"), "inner") + .select( + col("l.i").as("i"), + col("l.j").as("j"), + (col("l.value") - col("r.value")).as("value") + ).as[MatrixEntry] + } + + /** + * Computes the mean of each column. The result is transposed into a vector. + */ + override def colMean(matrix: DMatrix): DVector = { + matrix.groupBy(col("j")).agg( + org.apache.spark.sql.functions.mean(col("value")).as("value") + ).select( + col("j").as("i"), + col("value") + ).as[VectorEntry] + } + + override def size(matrix: DMatrix): (Long, Long) = { + val Row(m: Long, n: Long) = matrix.agg( + org.apache.spark.sql.functions.max(col("i")).cast(LongType) + 1, + org.apache.spark.sql.functions.max(col("j")).cast(LongType) + 1 + ).head + + (m, n) + } +} + +object BzMatrixOps extends MatrixOps[BDM[Double], BDV[Double]] { + /** + * alpha*A*x + beta*y + */ + override def gemv(A: BDM[Double], + x: BDV[Double], + yOpt: Option[BDV[Double]] = None, + alpha: Double = 1.0, + beta: Double = 1.0, + aTranspose: Boolean = false): BDV[Double] = { + val alphaAx = (if (aTranspose) A.t else A) * x * alpha + yOpt.map(_ * beta + alphaAx).getOrElse(alphaAx) + } + + override def transpose(matrix: BDM[Double]): BDM[Double] = { + matrix.t + } + + override def centerColumns(matrix: BDM[Double]): BDM[Double] = { + val copy = matrix.copy + 0 until copy.cols map { + i => + copy(::, i) -= mean(copy(::, i)) + } + + copy + } + + /** + * Computes the mean of each column. The result is transposed into a vector. + */ + override def colMean(matrix: BDM[Double]): BDV[Double] = { + BDV.tabulate(matrix.cols) { + i => mean(matrix(::, i)) + } + } + + override def size(matrix: BDM[Double]): (Long, Long) = (matrix.rows, matrix.cols) +} \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala new file mode 100644 index 0000000000..d8b3535efe --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala @@ -0,0 +1,179 @@ +package com.microsoft.azure.synapse.ml.causal.linalg + +import breeze.linalg.{norm, DenseVector => BDV, max => bmax, sum => bsum} +import breeze.numerics.{abs => babs, exp => bexp} +import breeze.stats.distributions.{Rand, RandBasis} +import breeze.stats.{mean => bmean} +import org.apache.spark.mllib.random.RandomRDDs.uniformRDD +import org.apache.spark.sql.functions.{coalesce, col, lit} +import org.apache.spark.sql.{Encoder, Encoders, Row, SparkSession} + +case class VectorEntry(i: Long, value: Double) + +trait VectorOps[T] { + /** + * sqrt( x'*x ) + */ + def nrm2(vector: T): Double + + /** + * alpha * x + y + */ + def axpy(x: T, yOpt: Option[T], alpha: Double = 1d): T + + def maxAbs(vec: T): Double + + def exp(vec: T): T + + def sum(vec: T): Double + + def elementwiseProduct(vec1: T, vec2: T): T + + def center(vec: T): T + + def mean(vec: T): Double + + def dot(vec1: T, vec2: T): Double + + def uniformRandom(size: Long, seed: Long = util.Random.nextLong): T +} + +object DVectorOps extends VectorOps[DVector] { + + private implicit val VectorEntryEncoder: Encoder[VectorEntry] = Encoders.product[VectorEntry] + + /** + * sqrt( x'*x ) + */ + def nrm2(vector: DVector): Double = { + val Row(sumOfSquares: Double) = vector + .agg(org.apache.spark.sql.functions.sum(col("value") * col("value"))) + .head + math.sqrt(sumOfSquares) + } + + /** + * alpha * x + y + */ + def axpy(x: DVector, yOpt: Option[DVector], alpha: Double = 1d): DVector = { + val ax = x.select(col("i"), (lit(alpha) * col("value")).as("value")) + + yOpt.map { + y => + ax.as("l") + .join(y.as("r"), col("l.i") === col("r.i"), "outer") + .select( + coalesce(col("l.i"), col("r.i")).as("i"), + (coalesce(col("l.value"), lit(0d)) + + coalesce(col("r.value"), lit(0d))).as("value") + ) + } + .getOrElse(ax) + .as[VectorEntry] + } + + def maxAbs(vec: DVector): Double = { + val Row(value: Double) = vec + .agg(org.apache.spark.sql.functions.max(org.apache.spark.sql.functions.abs(col("value")))) + .head + value + } + + def sum(vec: DVector): Double = { + val Row(value: Double) = vec + .agg(org.apache.spark.sql.functions.sum(col("value"))) + .head + value + } + + def elementwiseProduct(vec1: DVector, vec2: DVector): DVector = { + vec1.as("l").join(vec2.as("r"), col("l.i") === col("r.i"), "inner") + .select( + col("l.i").as("i"), + (col("l.value") * col("r.value")).alias("value") + ).as[VectorEntry] + } + + def uniformRandom(size: Long, seed: Long = util.Random.nextLong): DVector = { + val spark = SparkSession.active + import spark.implicits._ + val rdd = uniformRDD(spark.sparkContext, size, seed = seed).zipWithIndex.map(t => t.swap) + rdd.toDF("i", "value").as[VectorEntry] + } + + override def exp(vec: DVector): DVector = { + vec.select( + col("i"), + org.apache.spark.sql.functions.exp(col("value")).as("value") + ).as[VectorEntry] + } + + override def center(vec: DVector): DVector = { + val m = mean(vec) + vec.select( + col("i"), + (col("value") - lit(m)).as("value") + ).as[VectorEntry] + } + + override def mean(vec: DVector): Double = { + val Row(mean: Double) = vec.agg(org.apache.spark.sql.functions.mean(col("value"))).head + mean + } + + override def dot(vec1: DVector, vec2: DVector): Double = { + val Row(dot: Double) = vec1.as("l") + .join(vec2.as("r"), col("l.i") === col("r.i"), "inner") + .agg(org.apache.spark.sql.functions.sum(col("l.value") * col("r.value")).as("value")) + .head + dot + } +} + +object BzVectorOps extends VectorOps[BDV[Double]] { + /** + * sqrt( x'*x ) + */ + override def nrm2(vector: BDV[Double]): Double = norm(vector, 2) + + /** + * alpha * x + y + */ + override def axpy(x: BDV[Double], yOpt: Option[BDV[Double]], alpha: Double = 1d): BDV[Double] = { + val ax = x * alpha + yOpt.map(_ + ax).getOrElse(ax) + } + + override def maxAbs(vec: BDV[Double]): Double = { + bmax(babs(vec)) + } + + override def sum(vec: BDV[Double]): Double = { + bsum(vec) + } + + override def elementwiseProduct(vec1: BDV[Double], vec2: BDV[Double]): BDV[Double] = { + vec1 *:* vec2 + } + + override def uniformRandom(size: Long, seed: Long = util.Random.nextLong): BDV[Double] = { + val r = RandBasis.withSeed(seed.toInt).uniform + BDV.rand(size.toInt, r) + } + + override def exp(vec: BDV[Double]): BDV[Double] = { + bexp(vec) + } + + override def center(vec: BDV[Double]): BDV[Double] = { + vec - bmean(vec) + } + + override def mean(vec: BDV[Double]): Double = { + bmean(vec) + } + + override def dot(vec1: BDV[Double], vec2: BDV[Double]): Double = { + vec1 dot vec2 + } +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala new file mode 100644 index 0000000000..9be47d1d02 --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala @@ -0,0 +1,8 @@ +package com.microsoft.azure.synapse.ml.causal + +import org.apache.spark.sql.Dataset + +package object linalg { + type DVector = Dataset[VectorEntry] + type DMatrix = Dataset[MatrixEntry] +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala new file mode 100644 index 0000000000..98b4ef677d --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -0,0 +1,79 @@ +package com.microsoft.azure.synapse.ml.causal.opt + +import breeze.optimize.DiffFunction +import com.microsoft.azure.synapse.ml.causal.CacheOps +import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixOps, VectorOps} + +/** + * Solver for the following constrained least square problem: + * minimize ||Ax-b||^2^ + λ||x||^2^, s.t. 1^T^x = 1, 0 ≤ x ≤ 1 + * @param step the initial step size + * @param maxIter max number iterations allowed + * @param numIterNoChange max number of iteration without change in loss function allowed before termination. + * @param tol tolerance for loss function + */ +private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, + maxIter: Int, + numIterNoChange: Option[Int] = None, + tol: Double =1E-4 + )(implicit matrixOps: MatrixOps[TMat, TVec], + vectorOps: VectorOps[TVec], + cacheOps: CacheOps[TVec]) { + + if (step <= 0) throw new IllegalArgumentException("step must be positive") + if (maxIter <= 0) throw new IllegalArgumentException("maxIter must be positive") + if (tol <= 0) throw new IllegalArgumentException("tol must be positive") + if (!numIterNoChange.forall(_ > 0)) { + throw new IllegalArgumentException("numIterNoChange must be positive if defined.") + } + + private def getLossFunc(A: TMat, b: TVec, lambda: Double) + (implicit matrixOps: MatrixOps[TMat, TVec], vectorOps: VectorOps[TVec]) + : DiffFunction[TVec] = { + if (lambda < 0) throw new IllegalArgumentException("lambda must be positive.") + + (x: TVec) => { + // gemv: alpha*A*x + beta*y + + val error = matrixOps.gemv(A, x, Some(b), beta = -1) + val value = math.pow(vectorOps.nrm2(error), 2) + + lambda * math.pow(vectorOps.nrm2(x), 2) + + val grad = matrixOps.gemv( + A, + x = error, + yOpt = Some(x), + alpha = 2, + beta = 2 * lambda, + aTranspose = true + ) // 2 * A.t * (A*x - b) + 2 * lambda * x + + (value, grad) + } + } + + def solve(A: TMat, b: TVec, + lambda: Double = 0d, + fitIntercept: Boolean = false, + seed: Long = util.Random.nextLong): (TVec, Double) = { + + val aCentered = if (fitIntercept) matrixOps.centerColumns(A) else A + val bCentered = if (fitIntercept) vectorOps.center(b) else b + + val xSize = matrixOps.size(aCentered)._2 + + val lossFunc = getLossFunc(aCentered, bCentered, lambda) + val md = new MirrorDescent[TVec](lossFunc, step, maxIter, numIterNoChange, tol) + + val init = vectorOps.uniformRandom(xSize, seed) + val x = md.solve(init) + + if (fitIntercept){ + val colMean = matrixOps.colMean(A) + val bMean = vectorOps.mean(b) + (x, bMean - vectorOps.dot(x, colMean)) + } else { + (x, 0d) + } + } +} \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala new file mode 100644 index 0000000000..686d1770ab --- /dev/null +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala @@ -0,0 +1,136 @@ +package com.microsoft.azure.synapse.ml.causal.opt + +import breeze.optimize.DiffFunction +import com.microsoft.azure.synapse.ml.causal.CacheOps +import com.microsoft.azure.synapse.ml.causal.linalg.VectorOps +import org.apache.log4j.LogManager + +import scala.collection.mutable.ArrayBuffer + +/** + * Solver for optimization problem with constraint 1^T^x = 1, x > 0. + * is provided by vectorOps. + * @param vectorOps Provides implementation for vector operations + * @param cacheOps Provides optimization for Spark + */ +private[opt] class MirrorDescent[TVec](private val func: DiffFunction[TVec], + step: Double, + maxIter: Int, + numIterNoChange: Option[Int] = None, + tol: Double = 1E-4 + )(implicit vectorOps: VectorOps[TVec], cacheOps: CacheOps[TVec]) { + + if (step <= 0) throw new IllegalArgumentException("step must be positive") + if (maxIter <= 0) throw new IllegalArgumentException("maxIter must be positive") + if (tol <= 0) throw new IllegalArgumentException("tol must be positive") + if (!numIterNoChange.forall(_ > 0)) { + throw new IllegalArgumentException("numIterNoChange must be positive if defined.") + } + + private case class State(x: TVec, valueAt: Double, gradientAt: TVec) + + private object State{ + def apply(x: TVec): State = { + val (valueAt, gradientAt) = func.calculate(x) + State(x, valueAt, gradientAt) + } + } + + @transient private val history: ArrayBuffer[State] = ArrayBuffer.empty + @transient private lazy val logger = LogManager.getLogger(getClass.getName) + + private def solveSingleIteration(curr: State, i: Int): State = { + time { + logger.info(s"iteration: $i, loss: ${curr.valueAt}") + + val currStep = step / math.sqrt(i) + + val gradCheckpoint = checkpoint(curr.gradientAt) + + // t = (grad /:/ max(abs(grad)) * (-currStep)).map(math.exp) *:* x + val t = cache { + // normalize the gradients by max(abs(gradients)) for numerical stability + val normalized = vectorOps.axpy(gradCheckpoint, None, -currStep / vectorOps.maxAbs(gradCheckpoint)) + vectorOps.elementwiseProduct(vectorOps.exp(normalized), curr.x) + } + + // Renormalization to enforce constraint. + // t /:/ sum(t) + val x = checkpoint { + vectorOps.axpy(t, None, 1 / vectorOps.sum(t)) + } + + val state = State(x) + history.append(state) + state + } + } + + def solve(init: TVec): TVec = { + val initState = State(init) + history.append(initState) + + val (result, _) = (1 to maxIter).foldLeft((initState, false)) { + case ((curr, true), _) => + (curr, true) // terminated + + case ((curr, false), i) => + val result = solveSingleIteration(curr, i) + + val terminate = numIterNoChange.map(shouldTerminateEarly(_, tol)) + if (terminate.exists(_._1)) { + // numIterNoChange is defined, and should terminate + // return the best state so far and terminate + (terminate.get._2, true) + } else { + // next iteration + (result, false) + } + } + + result.x + } + + private def shouldTerminateEarly(numIterNoChange: Int, tol: Double): (Boolean, State) = { + assert(history.nonEmpty) + + if (history.size < numIterNoChange) { + // If history buffer does not have enough iterations, return false + (false, history.minBy(_.valueAt)) + } else { + val lastIterations = history.takeRight(numIterNoChange) + val minLastValueAt = lastIterations.map(_.valueAt).min + + val firstValueAtInLastIterations = lastIterations.head.valueAt + + // Check if the minimal loss is better (less) than the first iteration in the last numIterNoChange + // iterations by at least tol. Terminate if it's not improving. + if (minLastValueAt < firstValueAtInLastIterations - tol) + (false, lastIterations.minBy(_.valueAt)) + else + (true, lastIterations.minBy(_.valueAt)) + } + } + + + private def cache[R: CacheOps](block: => R): R = { + val result = block + val cacheOps = implicitly[CacheOps[R]] + cacheOps.cache(result) + } + + private def checkpoint[R: CacheOps](block: => R): R = { + val result = block + val cacheOps = implicitly[CacheOps[R]] + cacheOps.checkpoint(result) + } + + private def time[R](block: => R): R = { + val t0 = System.nanoTime() + val result = block // call-by-name + val t1 = System.nanoTime() + + logger.debug("Elapsed time: " + (t1 - t0) / 1E9 + "s") + result + } +} \ No newline at end of file From 2dbd4481b53d82e7c48126b54152c3d613ffab91 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 3 Oct 2023 21:40:31 -0700 Subject: [PATCH 02/41] add more params --- .../ml/causal/SyntheticControlEstimator.scala | 2 +- .../causal/SyntheticDiffInDiffEstimator.scala | 4 +-- .../ml/causal/SyntheticEstimator.scala | 5 +-- .../ml/causal/SyntheticEstimatorParams.scala | 35 +++++++++++++++++-- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 174078abb1..71c297e533 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -33,7 +33,7 @@ class SyntheticControlEstimator(override val uid: String) handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta = 0d, fitIntercept = false, - seed = 47 + seed = getSeed ) // join weights diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index d55acbd8fb..bfacde7d35 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -35,7 +35,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInD // fit time weights val (timeWeights, timeIntercept) = fitTimeWeights( handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), - seed = 47 + seed = getSeed ) // fit unit weights @@ -44,7 +44,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInD handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta, fitIntercept = true, - seed = 47 + seed = getSeed ) // join weights diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 6aa2b991c4..3640d5c100 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -28,8 +28,9 @@ trait SyntheticEstimator { private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, seed: Long): (DVector, Double) = { val size = matrixOps.size(A) - if (size._1 * size._2 <= 1000000) { - // If matrix size is less than 1M, collect the data on the driver node and solve it locally, where matrix-vector + if (size._1 * size._2 <= getLocalSolverThreshold) { + // If matrix size is less than LocalSolverThreshold (defaults to 1M), + // collect the data on the driver node and solve it locally, where matrix-vector // multiplication is done with breeze. It's much faster than solving with Spark at scale. implicit val bzMatrixOps: MatrixOps[BDM[Double], BDV[Double]] = BzMatrixOps implicit val bzVectorOps: VectorOps[BDV[Double]] = BzVectorOps diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala index dd4ea5b344..a66b8e6b0a 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala @@ -1,8 +1,10 @@ package com.microsoft.azure.synapse.ml.causal -import org.apache.spark.ml.param.{IntParam, Param, ParamValidators, Params} +import org.apache.spark.ml.param.{IntParam, LongParam, Param, ParamValidators, Params} import org.apache.spark.ml.param.shared.{HasMaxIter, HasStepSize, HasTol} +import scala.util.Random + trait SyntheticEstimatorParams extends Params with HasUnitCol with HasTimeCol @@ -28,6 +30,33 @@ trait SyntheticEstimatorParams extends Params def setNumIterNoChange(value: Int): this.type = set(numIterNoChange, value) + /** + * Param for creating the randomized initial solution to the constrained least square problem. + * @group expertParam + */ + final val seed = new LongParam(this, "seed", "seed for randomized initial solution") + + /** @group expertGetParam */ + def getSeed: Long = $(seed) + + /** @group expertGetParam */ + def setSeed(value: Long): this.type = set(seed, value) + + /** + * Param for deciding whether to collect part of data on driver node and solve the constrained least square problems + * locally on driver. + * @group expertParam + */ + final val localSolverThreshold = new LongParam(this, "localSolverThreshold", + "threshold for using local solver on driver node. Local solver is faster but relies on part of data " + + "being collected on driver node.") + + /** @group expertGetParam */ + def getLocalSolverThreshold: Long = $(localSolverThreshold) + + /** @group expertGetParam */ + def setLocalSolverThreshold(value: Long): this.type = set(localSolverThreshold, value) + def setMaxIter(value: Int): this.type = set(maxIter, value) def setStepSize(value: Double): this.type = set(stepSize, value) @@ -38,6 +67,8 @@ trait SyntheticEstimatorParams extends Params stepSize -> 0.5, tol -> 1E-3, maxIter -> 100, - handleMissingOutcome -> "zero" + handleMissingOutcome -> "zero", + seed -> Random.nextLong, + localSolverThreshold -> 1000 * 1000 ) } From 1de7aa1f1075bfc5b607450f98b9f1365270e1c9 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 4 Oct 2023 13:52:46 -0700 Subject: [PATCH 03/41] refactor Signed-off-by: Jason Wang --- .../ml/causal/BaseDiffInDiffEstimator.scala | 7 +- .../ml/causal/SyntheticEstimator.scala | 11 +--- .../synapse/ml/causal/linalg/MatrixOps.scala | 44 ++++++------- .../synapse/ml/causal/linalg/VectorOps.scala | 44 ++++++------- .../synapse/ml/causal/linalg/package.scala | 66 ++++++++++++++++++- 5 files changed, 110 insertions(+), 62 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 57808be98e..90b06fd9fc 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -5,9 +5,8 @@ import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.ml.param.ParamMap import org.apache.spark.ml.regression.LinearRegression import org.apache.spark.ml.{Estimator, Model} -import org.apache.spark.sql.functions.col -import org.apache.spark.sql.types.{LongType, StructField, StructType} -import org.apache.spark.sql.{DataFrame, Dataset, Row} +import org.apache.spark.sql.types.StructType +import org.apache.spark.sql.{DataFrame, Dataset} abstract class BaseDiffInDiffEstimator(override val uid: String) extends Estimator[DiffInDiffModel] @@ -17,7 +16,7 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) override def copy(extra: ParamMap): Estimator[DiffInDiffModel] = defaultCopy(extra) - val interactionCol = "interaction" + private[causal] val interactionCol = "interaction" private[causal] def fitLinearModel(did_data: DataFrame, weightCol: Option[String] = None) = { val assembler = new VectorAssembler() diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 3640d5c100..5e628fb6eb 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -22,7 +22,6 @@ trait SyntheticEstimator { private[causal] lazy val postTreatment = col(getPostTreatmentCol) private[causal] lazy val treatment = col(getTreatmentCol) private[causal] lazy val outcome = col(getOutcomeCol) - private[causal] val interactionCol = "interaction" private[causal] val weightsCol = "weights" private[causal] val epsilon = 1E-10 @@ -61,12 +60,7 @@ trait SyntheticEstimator { val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache val outcomePre = indexedPreControl - .select( - col(UnitIdxCol).as("i"), - col(TimeIdxCol).as("j"), - col(getOutcomeCol).as("value") - ) - .as[MatrixEntry] + .toDMatrix(UnitIdxCol, TimeIdxCol, getOutcomeCol) val outcomePostMean = indexedControlDf.filter(postTreatment) .groupBy(col(UnitIdxCol).as("i")) @@ -100,8 +94,7 @@ trait SyntheticEstimator { val outcomePreControl = indexedPreDf.filter(not(treatment)) - .select(col(TimeIdxCol).as("i"), col(UnitIdxCol).as("j"), outcome.as("value")) - .as[MatrixEntry] + .toDMatrix(TimeIdxCol, UnitIdxCol, getOutcomeCol) val outcomePreTreatMean = indexedPreDf.filter(treatment) .groupBy(col(TimeIdxCol).as("i")) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala index b9227b5415..42d156075c 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala @@ -2,7 +2,7 @@ package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} import breeze.stats.mean -import org.apache.spark.sql.functions.{col, lit, sum} +import org.apache.spark.sql.functions.{col, lit, sum, mean => smean, max => smax} import org.apache.spark.sql.types.LongType import org.apache.spark.sql.{Encoder, Encoders, Row} @@ -32,7 +32,6 @@ trait MatrixOps[TMatrix, TVector] { } object DMatrixOps extends MatrixOps[DMatrix, DVector] { - private implicit val MatrixEntryEncoder: Encoder[MatrixEntry] = Encoders.product[MatrixEntry] private implicit val VectorEntryEncoder: Encoder[VectorEntry] = Encoders.product[VectorEntry] /** @@ -52,31 +51,29 @@ object DMatrixOps extends MatrixOps[DMatrix, DVector] { .as[VectorEntry] yOpt.map { - y => - DVectorOps.axpy(y, Some(alphaAx), beta) - }.getOrElse(alphaAx) - .as[VectorEntry] + y => + DVectorOps.axpy(y, Some(alphaAx), beta) + }.getOrElse(alphaAx) } def transpose(matrix: DMatrix): DMatrix = { - matrix.select( - col("j").as("i"), - col("i").as("j"), - col("value") - ).as[MatrixEntry] + matrix.toDMatrix("j", "i", "value") } override def centerColumns(matrix: DMatrix): DMatrix = { val colMean = matrix.groupBy(col("j")).agg( - org.apache.spark.sql.functions.mean(col("value")).as("value") + smean(col("value")).as("value") ) - matrix.as("l").join(colMean.as("r"), col("l.j") === col("r.j"), "inner") - .select( - col("l.i").as("i"), - col("l.j").as("j"), - (col("l.value") - col("r.value")).as("value") - ).as[MatrixEntry] + matrix.as("l").join( + colMean.as("r"), + col("l.j") === col("r.j"), + "inner" + ).toDMatrix( + col("l.i"), + col("l.j"), + col("l.value") - col("r.value") + ) } /** @@ -84,17 +81,14 @@ object DMatrixOps extends MatrixOps[DMatrix, DVector] { */ override def colMean(matrix: DMatrix): DVector = { matrix.groupBy(col("j")).agg( - org.apache.spark.sql.functions.mean(col("value")).as("value") - ).select( - col("j").as("i"), - col("value") - ).as[VectorEntry] + smean(col("value")).as("value") + ).toDVector("j", "value") } override def size(matrix: DMatrix): (Long, Long) = { val Row(m: Long, n: Long) = matrix.agg( - org.apache.spark.sql.functions.max(col("i")).cast(LongType) + 1, - org.apache.spark.sql.functions.max(col("j")).cast(LongType) + 1 + smax(col("i")).cast(LongType) + 1, + smax(col("j")).cast(LongType) + 1 ).head (m, n) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala index d8b3535efe..755920e108 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala @@ -2,10 +2,12 @@ package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{norm, DenseVector => BDV, max => bmax, sum => bsum} import breeze.numerics.{abs => babs, exp => bexp} -import breeze.stats.distributions.{Rand, RandBasis} +import breeze.stats.distributions.RandBasis import breeze.stats.{mean => bmean} import org.apache.spark.mllib.random.RandomRDDs.uniformRDD -import org.apache.spark.sql.functions.{coalesce, col, lit} +import org.apache.spark.sql.functions.{ + coalesce, col, lit, exp => sexp, mean => smean, sum => ssum, max => smax, abs => sabs +} import org.apache.spark.sql.{Encoder, Encoders, Row, SparkSession} case class VectorEntry(i: Long, value: Double) @@ -47,7 +49,7 @@ object DVectorOps extends VectorOps[DVector] { */ def nrm2(vector: DVector): Double = { val Row(sumOfSquares: Double) = vector - .agg(org.apache.spark.sql.functions.sum(col("value") * col("value"))) + .agg(ssum(col("value") * col("value"))) .head math.sqrt(sumOfSquares) } @@ -56,42 +58,38 @@ object DVectorOps extends VectorOps[DVector] { * alpha * x + y */ def axpy(x: DVector, yOpt: Option[DVector], alpha: Double = 1d): DVector = { - val ax = x.select(col("i"), (lit(alpha) * col("value")).as("value")) + val ax = x.toDVector(col("i"), lit(alpha) * col("value")) yOpt.map { y => ax.as("l") .join(y.as("r"), col("l.i") === col("r.i"), "outer") - .select( - coalesce(col("l.i"), col("r.i")).as("i"), - (coalesce(col("l.value"), lit(0d)) - + coalesce(col("r.value"), lit(0d))).as("value") + .toDVector( + coalesce(col("l.i"), col("r.i")), + coalesce(col("l.value"), lit(0d)) + + coalesce(col("r.value"), lit(0d)) ) } .getOrElse(ax) - .as[VectorEntry] } def maxAbs(vec: DVector): Double = { val Row(value: Double) = vec - .agg(org.apache.spark.sql.functions.max(org.apache.spark.sql.functions.abs(col("value")))) + .agg(smax(sabs(col("value")))) .head value } def sum(vec: DVector): Double = { val Row(value: Double) = vec - .agg(org.apache.spark.sql.functions.sum(col("value"))) + .agg(ssum(col("value"))) .head value } def elementwiseProduct(vec1: DVector, vec2: DVector): DVector = { vec1.as("l").join(vec2.as("r"), col("l.i") === col("r.i"), "inner") - .select( - col("l.i").as("i"), - (col("l.value") * col("r.value")).alias("value") - ).as[VectorEntry] + .toDVector(col("l.i"), col("l.value") * col("r.value")) } def uniformRandom(size: Long, seed: Long = util.Random.nextLong): DVector = { @@ -102,29 +100,29 @@ object DVectorOps extends VectorOps[DVector] { } override def exp(vec: DVector): DVector = { - vec.select( + vec.toDVector( col("i"), - org.apache.spark.sql.functions.exp(col("value")).as("value") - ).as[VectorEntry] + sexp(col("value")) + ) } override def center(vec: DVector): DVector = { val m = mean(vec) - vec.select( + vec.toDVector( col("i"), - (col("value") - lit(m)).as("value") - ).as[VectorEntry] + col("value") - lit(m) + ) } override def mean(vec: DVector): Double = { - val Row(mean: Double) = vec.agg(org.apache.spark.sql.functions.mean(col("value"))).head + val Row(mean: Double) = vec.agg(smean(col("value"))).head mean } override def dot(vec1: DVector, vec2: DVector): Double = { val Row(dot: Double) = vec1.as("l") .join(vec2.as("r"), col("l.i") === col("r.i"), "inner") - .agg(org.apache.spark.sql.functions.sum(col("l.value") * col("r.value")).as("value")) + .agg(ssum(col("l.value") * col("r.value")).as("value")) .head dot } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala index 9be47d1d02..33a303c7d5 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala @@ -1,8 +1,72 @@ package com.microsoft.azure.synapse.ml.causal -import org.apache.spark.sql.Dataset +import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV, SparseVector => BSV} +import org.apache.spark.sql.functions.col +import org.apache.spark.sql.{Column, Dataset} package object linalg { type DVector = Dataset[VectorEntry] type DMatrix = Dataset[MatrixEntry] + + implicit class BDMConverter(val matrix: BDM[Double]) { + private lazy val spark = org.apache.spark.sql.SparkSession.active + import spark.implicits._ + + def toDMatrix: DMatrix = { + matrix.iterator.map { + case ((i, j), value) => (i, j, value) + }.toSeq.toDF("i", "j", "value").as[MatrixEntry] + } + } + + implicit class BDVConverter(val vector: BDV[Double]) { + private lazy val spark = org.apache.spark.sql.SparkSession.active + import spark.implicits._ + + def toDVector: DVector = { + vector.iterator.toSeq.toDF("i", "value").as[VectorEntry] + } + } + + implicit class DMatrixConverter(val matrix: DMatrix)(implicit matrixOps: MatrixOps[DMatrix, DVector]) { + def toBreeze: BDM[Double] = { + val size = matrixOps.size(matrix) + val bdm: BDM[Double] = BDM.zeros[Double](size._1.toInt, size._2.toInt) + matrix.collect.foreach { + case MatrixEntry(i, j, value) => + bdm(i.toInt, j.toInt) = value + } + bdm + } + } + + implicit class DVectorConverter(val vector: DVector)(implicit vectorOps: VectorOps[DVector]) { + def toBreeze: BDV[Double] = { + val values = vector.collect.map(entry => (entry.i.toInt, entry.value)).toSeq + BSV(values.length)(values: _*).toDenseVector + } + } + + implicit class DataFrameConverter(val df: Dataset[_]) { + import df.sparkSession.implicits._ + + def toDMatrix(rowIdxCol: String, columnIdxCol: String, valueCol: String): DMatrix = { + toDMatrix(col(rowIdxCol), col(columnIdxCol), col(valueCol)) + } + + def toDMatrix(rowIdxCol: Column, columnIdxCol: Column, valueCol: Column): DMatrix = { + df.select(rowIdxCol.as("i"), columnIdxCol.as("j"), valueCol.as("value")) + .as[MatrixEntry] + } + + def toDVector(idxCol: String, valueCol: String): DVector = { + df.select(col(idxCol).as("i"), col(valueCol).as("value")) + .as[VectorEntry] + } + + def toDVector(idxCol: Column, valueCol: Column): DVector = { + df.select(idxCol.as("i"), valueCol.as("value")) + .as[VectorEntry] + } + } } From 98a66599c3d67ee86f8964e4d8c7ef159d572419 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 4 Oct 2023 16:47:39 -0700 Subject: [PATCH 04/41] adding unit tests for linalg --- .../ml/causal/SyntheticControlEstimator.scala | 5 +- .../ml/causal/linalg/VerifyMatrixOps.scala | 48 ++++++++++++ .../ml/causal/linalg/VerifyVectorOps.scala | 73 +++++++++++++++++++ 3 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 71c297e533..c20ef4efc4 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -37,9 +37,8 @@ class SyntheticControlEstimator(override val uid: String) ) // join weights - val Row(_: Long, u: Long) = df.agg( - countDistinct(when(postTreatment, col(getTimeCol))), - countDistinct(when(treatment, col(getUnitCol))), + val Row(u: Long) = df.agg( + countDistinct(when(treatment, col(getUnitCol))) ).head val indexedDf = df.join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala new file mode 100644 index 0000000000..824471e74c --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala @@ -0,0 +1,48 @@ +package com.microsoft.azure.synapse.ml.causal.linalg + +import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} +import breeze.stats.distributions.RandBasis +import com.microsoft.azure.synapse.ml.core.test.base.TestBase +import org.scalactic.Equality + +class VerifyMatrixOps extends TestBase { + spark + + private lazy val testBzVector1 = BDV.rand(20, RandBasis.withSeed(47).uniform) + private lazy val testDVector1 = testBzVector1.toDVector + + private lazy val testBzVector2 = BDV.rand(10, RandBasis.withSeed(61).gaussian) + private lazy val testDVector2 = testBzVector2.toDVector + + private lazy val testBzMatrix = BDM.rand(10, 20, RandBasis.withSeed(79).gaussian) + private lazy val testDMatrix = testBzMatrix.toDMatrix + + implicit val BDVEquality: Equality[BDV[Double]] = breezeVectorEq(1E-8) + implicit val BDMEquality: Equality[BDM[Double]] = breezeMatrixEq(1E-8) + + test("MatrixOps.size computes correctly") { + assert(DMatrixOps.size(testDMatrix) === BzMatrixOps.size(testBzMatrix)) + } + + test("MatrixOps.transpose computes correctly") { + implicit val matrixOps: MatrixOps[DMatrix, DVector] = DMatrixOps + assert(DMatrixOps.transpose(testDMatrix).toBreeze === BzMatrixOps.transpose(testBzMatrix)) + } + + test("MatrixOps.colMean computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + assert(DMatrixOps.colMean(testDMatrix).toBreeze === BzMatrixOps.colMean(testBzMatrix)) + } + + test("MatrixOps.centerColumns computes correctly") { + implicit val matrixOps: MatrixOps[DMatrix, DVector] = DMatrixOps + assert(DMatrixOps.centerColumns(testDMatrix).toBreeze === BzMatrixOps.centerColumns(testBzMatrix)) + } + + test("MatrixOps.gemv computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val result1 = DMatrixOps.gemv(testDMatrix, testDVector1, Some(testDVector2), 2.0, 3.0).toBreeze + val result2 = BzMatrixOps.gemv(testBzMatrix, testBzVector1, Some(testBzVector2), 2.0, 3.0) + assert(result1 === result2) + } +} \ No newline at end of file diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala new file mode 100644 index 0000000000..5754035e1a --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala @@ -0,0 +1,73 @@ +package com.microsoft.azure.synapse.ml.causal.linalg + +import breeze.linalg.{DenseVector => BDV} +import breeze.stats.distributions.RandBasis +import com.microsoft.azure.synapse.ml.core.test.base.TestBase +import org.scalactic.{Equality, TolerantNumerics} + +class VerifyVectorOps extends TestBase { + spark + + private lazy val testBzVector1 = BDV.rand(20, RandBasis.withSeed(47).uniform) + private lazy val testDVector1 = testBzVector1.toDVector + + private lazy val testBzVector2 = BDV.rand(20, RandBasis.withSeed(47).gaussian) + private lazy val testDVector2 = testBzVector2.toDVector + + implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + implicit val BDVEquality: Equality[BDV[Double]] = breezeVectorEq(1E-8) + + + test("VectorOps.nrm2 computes correctly") { + assert(DVectorOps.nrm2(testDVector1) === BzVectorOps.nrm2(testBzVector1)) + } + + test("VectorOps.sum computes correctly") { + assert(DVectorOps.sum(testDVector1) === BzVectorOps.sum(testBzVector1)) + } + + test("VectorOps.mean computes correctly") { + assert(DVectorOps.mean(testDVector1) === BzVectorOps.mean(testBzVector1)) + } + + test("VectorOps.dot computes correctly") { + assert(DVectorOps.dot(testDVector1, testDVector2) === BzVectorOps.dot(testBzVector1, testBzVector2)) + } + + test("VectorOps.maxAbs computes correctly") { + assert(DVectorOps.maxAbs(testDVector2) === BzVectorOps.maxAbs(testBzVector2)) + } + + test("VectorOps.axpy computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val result = DVectorOps.axpy(testDVector1, Some(testDVector2), 2.5) + assert(result.toBreeze === BzVectorOps.axpy(testBzVector1, Some(testBzVector2), 2.5)) + } + + test("VectorOps.center computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val result = DVectorOps.center(testDVector1) + assert(result.toBreeze === BzVectorOps.center(testBzVector1)) + } + + test("VectorOps.elementwiseProduct computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val result = DVectorOps.elementwiseProduct(testDVector1, testDVector2) + assert(result.toBreeze === BzVectorOps.elementwiseProduct(testBzVector1, testBzVector2)) + } + + test("VectorOps.exp computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val result = DVectorOps.exp(testDVector1) + assert(result.toBreeze === BzVectorOps.exp(testBzVector1)) + } + + test("VectorOps.uniformRandom computes correctly") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val result1 = DVectorOps.uniformRandom(20, 47).toBreeze + val result2 = BzVectorOps.uniformRandom(20, 47) + assert(result1.length === result2.length) + assert(result1.forall(0 <= _ && _ <= 1)) + assert(result2.forall(0 <= _ && _ <= 1)) + } +} From 29deccc3fba8bdcf983ae3238941efacced3fa4d Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 4 Oct 2023 21:54:43 -0700 Subject: [PATCH 05/41] more unit tests --- .../causal/opt/ConstrainedLeastSquare.scala | 2 +- .../synapse/ml/causal/opt/MirrorDescent.scala | 4 +- .../opt/VerifyConstrainedLeastSquare.scala | 54 ++++++++++++++++ .../ml/causal/opt/VerifyMirrorDescent.scala | 61 +++++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala index 98b4ef677d..12419fa6be 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -27,7 +27,7 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, throw new IllegalArgumentException("numIterNoChange must be positive if defined.") } - private def getLossFunc(A: TMat, b: TVec, lambda: Double) + private[causal] def getLossFunc(A: TMat, b: TVec, lambda: Double) (implicit matrixOps: MatrixOps[TMat, TVec], vectorOps: VectorOps[TVec]) : DiffFunction[TVec] = { if (lambda < 0) throw new IllegalArgumentException("lambda must be positive.") diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala index 686d1770ab..bfa4bccf9f 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala @@ -27,7 +27,7 @@ private[opt] class MirrorDescent[TVec](private val func: DiffFunction[TVec], throw new IllegalArgumentException("numIterNoChange must be positive if defined.") } - private case class State(x: TVec, valueAt: Double, gradientAt: TVec) + private[causal] case class State(x: TVec, valueAt: Double, gradientAt: TVec) private object State{ def apply(x: TVec): State = { @@ -36,7 +36,7 @@ private[opt] class MirrorDescent[TVec](private val func: DiffFunction[TVec], } } - @transient private val history: ArrayBuffer[State] = ArrayBuffer.empty + @transient private[causal] val history: ArrayBuffer[State] = ArrayBuffer.empty @transient private lazy val logger = LogManager.getLogger(getClass.getName) private def solveSingleIteration(curr: State, i: Int): State = { diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala new file mode 100644 index 0000000000..a64d56a735 --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala @@ -0,0 +1,54 @@ +package com.microsoft.azure.synapse.ml.causal.opt + +import breeze.stats.distributions.RandBasis +import breeze.linalg.{sum, DenseMatrix => BDM, DenseVector => BDV} +import com.microsoft.azure.synapse.ml.causal._ +import com.microsoft.azure.synapse.ml.causal.linalg._ +import com.microsoft.azure.synapse.ml.core.test.base.TestBase +import org.scalactic.{Equality, TolerantNumerics} + +class VerifyConstrainedLeastSquare extends TestBase { + private val matrixA = BDM.rand(100, 50, RandBasis.withSeed(47).uniform) + private val vectorB = BDV.rand(100, RandBasis.withSeed(59).uniform) + private implicit val matrixOps: MatrixOps[BDM[Double], BDV[Double]] = BzMatrixOps + private implicit val vectorOps: VectorOps[BDV[Double]] = BzVectorOps + private implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps + private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + + test("Fit CLS without intercept") { + val cls = new ConstrainedLeastSquare(1.5, 1000) + val solution = cls.solve(matrixA, vectorB, seed = 47) + assert(solution._2 === 0d) + assert(sum(solution._1) === 1.0) + assert(solution._1.forall(0 <= _ && _ <= 1)) + val lossFunc = cls.getLossFunc(matrixA, vectorB, 0d) + assert(lossFunc(solution._1) === 6.0854513873062155) + } + + test("Fit CLS with intercept") { + val cls = new ConstrainedLeastSquare(1.5, 1000) + val solution = cls.solve(matrixA, vectorB, fitIntercept = true, seed = 47) + assert(solution._2 === 0.025653849372480997) + assert(sum(solution._1) === 1.0) + assert(solution._1.forall(0 <= _ && _ <= 1)) + + val error = vectorOps.axpy( + vectorB, + Some(matrixOps.gemv(matrixA, solution._1, Some(BDV.fill(vectorB.size)(solution._2)))), + -1 + ) + + val loss = math.pow(vectorOps.nrm2(error), 2) + assert(loss === 6.027842402810429) + } + + test("Fit CLS with L2 regularization") { + val cls = new ConstrainedLeastSquare(1.5, 1000) + val solution = cls.solve(matrixA, vectorB, lambda = 1.5, seed = 47) + assert(solution._2 === 0d) + assert(sum(solution._1) === 1.0) + assert(solution._1.forall(0 <= _ && _ <= 1)) + val lossFunc = cls.getLossFunc(matrixA, vectorB, 1.5) + assert(lossFunc(solution._1) === 6.219637309765119) + } +} diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala new file mode 100644 index 0000000000..de89111a03 --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala @@ -0,0 +1,61 @@ +package com.microsoft.azure.synapse.ml.causal.opt + +import breeze.linalg.{sum, DenseMatrix => BDM, DenseVector => BDV} +import breeze.optimize.DiffFunction +import breeze.stats.distributions.RandBasis +import com.microsoft.azure.synapse.ml.causal._ +import com.microsoft.azure.synapse.ml.causal.linalg._ +import com.microsoft.azure.synapse.ml.core.test.base.TestBase +import org.scalactic.{Equality, TolerantNumerics} + +class VerifyMirrorDescent extends TestBase { + private val matrixA = BDM.rand(100, 50, RandBasis.withSeed(47).uniform) + private val vectorB = BDV.rand(100, RandBasis.withSeed(59).uniform) + private implicit val matrixOps: MatrixOps[BDM[Double], BDV[Double]] = BzMatrixOps + private implicit val vectorOps: VectorOps[BDV[Double]] = BzVectorOps + private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + + private def getLossFunc(A: BDM[Double], b: BDV[Double]) + : DiffFunction[BDV[Double]] = { + (x: BDV[Double]) => { + val error = matrixOps.gemv(A, x, Some(b), beta = -1) + val value = math.pow(vectorOps.nrm2(error), 2) + + val grad = matrixOps.gemv( + A, + x = error, + alpha = 2, + aTranspose = true + ) // 2 * A.t * (A*x - b) + + (value, grad) + } + } + + private val loss = getLossFunc(matrixA, vectorB) + + test("MirrorDescent finds optimal constrained solution") { + implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps + val md = new MirrorDescent(loss, 0.5, 1000) + + val initial = BDV.rand[Double](50, RandBasis.withSeed(47).uniform) + val solution = md.solve(initial) + assert(sum(solution) === 1.0) + assert(solution.forall(0 <= _ && _ <= 1)) + assert(loss(solution) === 6.087454460148611) + val lossHistory = md.history.map(_.valueAt) + assert(lossHistory.length === 1001) + } + + test("MirrorDescent finds constrained solution with early stopping") { + implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps + val md = new MirrorDescent(loss, 0.5, 1000, Some(5), tol = 1E-2) + val initial = BDV.rand[Double](50, RandBasis.withSeed(47).uniform) + val solution = md.solve(initial) + assert(sum(solution) === 1.0) + assert(solution.forall(0 <= _ && _ <= 1)) + assert(loss(solution) === 6.1839598830745794) + val lossHistory = md.history.map(_.valueAt) + assert(lossHistory.length === 59) + } +} From b7293c2315f9470dd932c615d237d1694d2cc273 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 6 Oct 2023 00:36:51 -0700 Subject: [PATCH 06/41] Unit test for DiffInDiffEstimator --- .../ml/causal/BaseDiffInDiffEstimator.scala | 62 ++++++++++++++++--- .../ml/causal/DiffInDiffEstimator.scala | 20 ++++-- .../ml/causal/SyntheticControlEstimator.scala | 5 +- .../causal/SyntheticDiffInDiffEstimator.scala | 5 +- .../ml/causal/VerifyDiffInDiffEstimator.scala | 39 ++++++++++++ 5 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 90b06fd9fc..30054aebff 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -1,24 +1,43 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.DVector +import org.apache.spark.SparkException import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.ml.param.ParamMap import org.apache.spark.ml.regression.LinearRegression -import org.apache.spark.ml.{Estimator, Model} -import org.apache.spark.sql.types.StructType +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable, Estimator, Model} +import org.apache.spark.sql.types.{BooleanType, NumericType, StructField, StructType} import org.apache.spark.sql.{DataFrame, Dataset} abstract class BaseDiffInDiffEstimator(override val uid: String) extends Estimator[DiffInDiffModel] with DiffInDiffEstimatorParams { - override def transformSchema(schema: StructType): StructType = throw new NotImplementedError + private def validateFieldNumericOrBooleanType(field: StructField): Unit = { + val dataType = field.dataType + require(dataType.isInstanceOf[NumericType] || dataType == BooleanType, + s"Column ${field.name} must be numeric type or boolean type, but got $dataType instead.") + } + + private def validateFieldNumericType(field: StructField): Unit = { + val dataType = field.dataType + require(dataType.isInstanceOf[NumericType], + s"Column ${field.name} must be numeric type, but got $dataType instead.") + } + + override def transformSchema(schema: StructType): StructType = { + validateFieldNumericOrBooleanType(schema(getPostTreatmentCol)) + validateFieldNumericOrBooleanType(schema(getTreatmentCol)) + validateFieldNumericType(schema(getOutcomeCol)) + schema + } override def copy(extra: ParamMap): Estimator[DiffInDiffModel] = defaultCopy(extra) private[causal] val interactionCol = "interaction" - private[causal] def fitLinearModel(did_data: DataFrame, weightCol: Option[String] = None) = { + private[causal] def fitLinearModel(df: DataFrame, weightCol: Option[String] = None) = { val assembler = new VectorAssembler() .setInputCols(Array(getPostTreatmentCol, getTreatmentCol, interactionCol)) .setOutputCol("features") @@ -34,7 +53,7 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) .setLoss("squaredError") .setRegParam(0.0) - assembler.transform _ andThen regression.fit apply did_data + assembler.transform _ andThen regression.fit apply df } } @@ -42,10 +61,33 @@ case class DiffInDiffSummary(treatmentEffect: Double, standardError: Double, timeWeights: Option[DVector] = None, timeIntercept: Option[Double] = None, unitWeights: Option[DVector] = None, unitIntercept: Option[Double] = None) -class DiffInDiffModel(override val uid: String, val summary: DiffInDiffSummary) extends Model[DiffInDiffModel] { - override def copy(extra: ParamMap): DiffInDiffModel = defaultCopy(extra) +class DiffInDiffModel(override val uid: String) + extends Model[DiffInDiffModel] + with ComplexParamsWritable { + + def this() = this(Identifiable.randomUID("did")) - override def transform(dataset: Dataset[_]): DataFrame = throw new NotImplementedError + private final var summary: Option[DiffInDiffSummary] = None + + def getSummary: DiffInDiffSummary = summary.getOrElse { + throw new SparkException( + s"No summary available for this ${this.getClass.getSimpleName}") + } + + private[causal] def setSummary(summary: Option[DiffInDiffSummary]): this.type = { + this.summary = summary + this + } + + override def copy(extra: ParamMap): DiffInDiffModel = { + copyValues(new DiffInDiffModel(uid)) + .setSummary(this.summary) + .setParent(parent) + } + + override def transform(dataset: Dataset[_]): DataFrame = dataset.toDF + + override def transformSchema(schema: StructType): StructType = schema +} - override def transformSchema(schema: StructType): StructType = throw new NotImplementedError -} \ No newline at end of file +object DiffInDiffModel extends ComplexParamsReadable[DiffInDiffModel] \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index 3c2d82b82a..472f3e1afe 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -1,16 +1,22 @@ package com.microsoft.azure.synapse.ml.causal +import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.logging.SynapseMLLogging import org.apache.spark.ml.util.Identifiable +import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.sql._ import org.apache.spark.sql.functions.col import org.apache.spark.sql.types._ class DiffInDiffEstimator(override val uid: String) - extends BaseDiffInDiffEstimator(uid) { + extends BaseDiffInDiffEstimator(uid) + with ComplexParamsWritable + with Wrappable + with SynapseMLLogging { def this() = this(Identifiable.randomUID("did")) - override def fit(dataset: Dataset[_]): DiffInDiffModel = { + override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ val postTreatment = col(getPostTreatmentCol) val treatment = col(getTreatmentCol) val outcome = col(getOutcomeCol) @@ -27,7 +33,11 @@ class DiffInDiffEstimator(override val uid: String) val treatmentEffect = linearModel.coefficients(2) val standardError = linearModel.summary.coefficientStandardErrors(2) val summary = DiffInDiffSummary(treatmentEffect, standardError) - val model = new DiffInDiffModel(this.uid, summary).setParent(this) - copyValues(model) - } + + copyValues(new DiffInDiffModel(this.uid)) + .setSummary(Some(summary)) + .setParent(this) + }, dataset.columns.length) } + +object DiffInDiffEstimator extends ComplexParamsReadable[DiffInDiffEstimator] \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index c20ef4efc4..7fa69e92b6 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -75,7 +75,8 @@ class SyntheticControlEstimator(override val uid: String) Some(unitIntercept) ) - val model = new DiffInDiffModel(this.uid, summary).setParent(this) - copyValues(model) + copyValues(new DiffInDiffModel(this.uid)) + .setSummary(Some(summary)) + .setParent(this) } } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index bfacde7d35..a2f7a47112 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -91,7 +91,8 @@ class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInD Some(unitIntercept) ) - val model = new DiffInDiffModel(this.uid, summary).setParent(this) - copyValues(model) + copyValues(new DiffInDiffModel(this.uid)) + .setSummary(Some(summary)) + .setParent(this) } } \ No newline at end of file diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala new file mode 100644 index 0000000000..739180f412 --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala @@ -0,0 +1,39 @@ +package com.microsoft.azure.synapse.ml.causal + +import breeze.stats.distributions.RandBasis +import com.microsoft.azure.synapse.ml.core.test.fuzzing.{EstimatorFuzzing, TestObject} +import org.apache.spark.ml.util.MLReadable + +class VerifyDiffInDiffEstimator + extends EstimatorFuzzing[DiffInDiffEstimator] { + + import spark.implicits._ + + private lazy val rand = RandBasis.withSeed(47).uniform + private lazy val data = + (1 to 100).map(_ => (0, 0, rand.sample + 2)) ++ + (1 to 100).map(_ => (0, 1, rand.sample + 3)) ++ + (1 to 100).map(_ => (1, 0, rand.sample + 5)) ++ + (1 to 100).map(_ => (1, 1, rand.sample + 8)) + + private lazy val df = data.toDF("treatment", "postTreatment", "outcome") + private lazy val estimator = new DiffInDiffEstimator() + .setTreatmentCol("treatment") + .setPostTreatmentCol("postTreatment") + .setOutcomeCol("outcome") + + test("DiffInDiffEstimator can estimate the treatment effect") { + val summary = estimator.fit(df).getSummary + // treatment effect is approximately 2 + assert(math.abs(summary.treatmentEffect - 2) < 1E-2) + } + + override def testObjects(): Seq[TestObject[DiffInDiffEstimator]] = Seq( + new TestObject(estimator, df, df) + ) + + override def reader: MLReadable[_] = DiffInDiffEstimator + + override def modelReader: MLReadable[_] = DiffInDiffModel +} + From 0eb6b62f4faa973dcd114f0770171a691d4c8de8 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 6 Oct 2023 11:32:23 -0700 Subject: [PATCH 07/41] more unit tests --- .../ml/causal/SyntheticEstimator.scala | 66 ++++++++------ .../ml/causal/VerifySyntheticEstimator.scala | 88 +++++++++++++++++++ 2 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 5e628fb6eb..137cd4f860 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -36,7 +36,7 @@ trait SyntheticEstimator { implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps val bzA = convertToBDM(A.collect()) - val bzb = convertToBDV(b.collect()) + val bzb = convertToBDV(b.collect()) // b is assumed to be non-sparse val solver = new ConstrainedLeastSquare[BDM[Double], BDV[Double]]( step = this.getStepSize, maxIter = this.getMaxIter, numIterNoChange = getNumIterNoChange, tol = this.getTol @@ -124,7 +124,7 @@ trait SyntheticEstimator { // Only impute the control_pre group. val controlPre = indexed.filter(not(treatment) and not(postTreatment)) - val imputed = imputeTimeSeries(controlPre, maxTimeLength) + val imputed = imputeTimeSeries(controlPre, maxTimeLength, getOutcomeCol) .withColumn(getTreatmentCol, lit(false)) .withColumn(getPostTreatmentCol, lit(false)) @@ -141,48 +141,57 @@ trait SyntheticEstimator { ) } } +} - private def imputeTimeSeries(df: DataFrame, maxTimeLength: Int): DataFrame = { - val impute: UserDefinedFunction = udf((timeIndex: Seq[Int], values: Seq[Double]) => { - val valueMap = timeIndex.zip(values).toMap - val range = 0 until maxTimeLength - range.map { i => valueMap.getOrElse(i, imputeMissingValue(i, valueMap, range)) } - }) +object SyntheticEstimator { + val UnitIdxCol = "Unit_idx" + val TimeIdxCol = "Time_idx" - df.groupBy(UnitIdxCol).agg( - collect_list(TimeIdxCol).as(TimeIdxCol), collect_list(outcome).as(getOutcomeCol) - ) - .select(col(UnitIdxCol), explode(impute(col(TimeIdxCol), outcome)).as("exploded")) + private[causal] def imputeTimeSeries(df: DataFrame, maxTimeLength: Int, outcomeCol: String): DataFrame = { + val impute: UserDefinedFunction = udf(imputeMissingValues(maxTimeLength) _) + + df + // zip time and outcomes + .select(col(UnitIdxCol), struct(col(TimeIdxCol), col(outcomeCol)).as(outcomeCol)) + .groupBy(UnitIdxCol) + // construct a map of time -> outcome per unit + .agg(map_from_entries(collect_set(col(outcomeCol))).as(outcomeCol)) + // impute and explode back + .select(col(UnitIdxCol), explode(impute(col(outcomeCol))).as("exploded")) .select( col(UnitIdxCol), col("exploded._1").as(TimeIdxCol), - col("exploded._2").as(getOutcomeCol) + col("exploded._2").as(outcomeCol) ) } -} -object SyntheticEstimator { - val UnitIdxCol = "Unit_idx" - val TimeIdxCol = "Time_idx" + private[causal] def imputeMissingValues(maxLength: Int)(values: Map[Int, Double]): Seq[(Int, Double)] = { + val range = 0 until maxLength - private def imputeMissingValue(i: Int, valueMap: Map[Int, Double], range: Range): (Int, Double) = { // Find the nearest neighbors using collectFirst - def findNeighbor(direction: Int): Option[Double] = { + def findNeighbor(direction: Int, curr: Int): Option[Double] = { val searchRange = if (direction == -1) range.reverse else range searchRange.collectFirst { - case j if j * direction > i * direction && valueMap.contains(j) => valueMap(j) + case j if j * direction > curr * direction && values.contains(j) => values(j) } } - (findNeighbor(-1), findNeighbor(1)) match { - case (Some(left), Some(right)) => (i, (left + right) / 2.0) - case (Some(left), None) => (i, left) - case (None, Some(right)) => (i, right) - case (None, None) => (i, 0.0) // Should never happen + range.map { + i => + if (values.contains(i)) + (i, values(i)) + else { + (findNeighbor(-1, i), findNeighbor(1, i)) match { + case (Some(left), Some(right)) => (i, (left + right) / 2.0) + case (Some(left), None) => (i, left) + case (None, Some(right)) => (i, right) + case (None, None) => (i, 0.0) // Should never happen + } + } } } - private def convertToBDM(mat: Array[MatrixEntry]): BDM[Double] = { + private[causal] def convertToBDM(mat: Array[MatrixEntry]): BDM[Double] = { val numRows = mat.map(_.i).max.toInt + 1 val numCols = mat.map(_.j).max.toInt + 1 val denseMatrix = BDM.zeros[Double](numRows, numCols) @@ -193,7 +202,8 @@ object SyntheticEstimator { denseMatrix } - private def convertToBDV(vec: Array[VectorEntry]): BDV[Double] = { + private[causal] def convertToBDV(vec: Array[VectorEntry]): BDV[Double] = { + // assuming vec is not sparse. val length = vec.map(_.i).max.toInt + 1 val denseVector = BDV.zeros[Double](length) @@ -204,7 +214,7 @@ object SyntheticEstimator { denseVector } - private def assignRowIndex(df: DataFrame, colName: String): DataFrame = { + private[causal] def assignRowIndex(df: DataFrame, colName: String): DataFrame = { df.sqlContext.createDataFrame( df.rdd.zipWithIndex.map(element => Row.fromSeq(Seq(element._2) ++ element._1.toSeq) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala new file mode 100644 index 0000000000..ef3bc45ae7 --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala @@ -0,0 +1,88 @@ +package com.microsoft.azure.synapse.ml.causal + +import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixEntry, VectorEntry} +import com.microsoft.azure.synapse.ml.core.test.base.TestBase +import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} + +class VerifySyntheticEstimator extends TestBase { + import spark.implicits._ + + test("imputeTimeSeries") { + val df = Seq( + (0, 3, 1.0), + (0, 5, 2.0), + (0, 8, 5.0) + ) toDF (SyntheticEstimator.UnitIdxCol, SyntheticEstimator.TimeIdxCol, "Outcome") + + val result = SyntheticEstimator.imputeTimeSeries(df, 10, "Outcome") + + val expected = Seq( + (0, 0, 1.0), + (0, 1, 1.0), + (0, 2, 1.0), + (0, 3, 1.0), + (0, 4, 1.5), + (0, 5, 2.0), + (0, 6, 3.5), + (0, 7, 3.5), + (0, 8, 5.0), + (0, 9, 5.0) + ) toDF (SyntheticEstimator.UnitIdxCol, SyntheticEstimator.TimeIdxCol, "Outcome") + + super.verifyResult(expected, result) + } + + test("imputeMissingValues") { + val testData = Map(3 -> 1.0, 5 -> 2.0, 8 -> 5.0) + val imputed = SyntheticEstimator.imputeMissingValues(10)(testData).toMap + assert( + imputed === Map( + 0 -> 1.0, + 1 -> 1.0, + 2 -> 1.0, + 3 -> 1.0, + 4 -> 1.5, + 5 -> 2.0, + 6 -> 3.5, + 7 -> 3.5, + 8 -> 5.0, + 9 -> 5.0 + ) + ) + } + + test("convertToBDM") { + val bdm = SyntheticEstimator.convertToBDM(Array( + MatrixEntry(0, 0, 3.0), + MatrixEntry(1, 1, 3.0), + MatrixEntry(2, 2, 3.0) + )) + + assert( + bdm === (BDM.eye[Double](3) *:* 3d) + ) + } + + test("convertToBDV") { + val bdv = SyntheticEstimator.convertToBDV(Array( + VectorEntry(0, 3.0), + VectorEntry(1, 3.0), + VectorEntry(2, 3.0) + )) + + assert( + bdv === (BDV.ones[Double](3) *:* 3d) + ) + } + + test("createIndex") { + val df = super.makeBasicDF() + val index = SyntheticEstimator.createIndex(df, "numbers", "index") + val expected = Seq( + (0, 0), + (1, 1), + (2, 2) + ) toDF("index", "numbers") + super.verifyResult(expected, index) + } +} From 5e936361cba054dde8c1cfe122f9c5044225fa61 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 6 Oct 2023 17:55:42 -0700 Subject: [PATCH 08/41] unit test for SyntheticControlEstimator --- .../ml/causal/SyntheticControlEstimator.scala | 20 +++- .../causal/SyntheticDiffInDiffEstimator.scala | 6 +- .../ml/causal/SyntheticEstimator.scala | 19 ++-- .../ml/causal/SyntheticEstimatorParams.scala | 17 +--- .../synapse/ml/causal/linalg/VectorOps.scala | 50 +++++----- .../causal/opt/ConstrainedLeastSquare.scala | 5 +- .../VerifySyntheticControlEstimator.scala | 97 +++++++++++++++++++ .../ml/causal/linalg/VerifyVectorOps.scala | 6 +- .../opt/VerifyConstrainedLeastSquare.scala | 40 ++++++-- .../ml/causal/opt/VerifyMirrorDescent.scala | 10 +- 10 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 7fa69e92b6..671869e34e 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -1,22 +1,31 @@ package com.microsoft.azure.synapse.ml.causal +import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.logging.SynapseMLLogging +import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.functions._ -import org.apache.spark.sql.types.IntegerType +import org.apache.spark.sql.types.{BooleanType, IntegerType} import org.apache.spark.sql.{Dataset, Row} class SyntheticControlEstimator(override val uid: String) extends BaseDiffInDiffEstimator(uid) with SyntheticEstimator with DiffInDiffEstimatorParams - with SyntheticEstimatorParams { + with SyntheticEstimatorParams + with ComplexParamsWritable + with Wrappable + with SynapseMLLogging { import SyntheticEstimator._ def this() = this(Identifiable.randomUID("sc")) override def fit(dataset: Dataset[_]): DiffInDiffModel = { - val df = dataset.toDF + val df = dataset + .withColumn(getTreatmentCol, treatment.cast(BooleanType)) + .withColumn(getPostTreatmentCol, postTreatment.cast(BooleanType)) + .toDF val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache val controlPreDf = controlDf.filter(not(postTreatment)).cache @@ -32,8 +41,7 @@ class SyntheticControlEstimator(override val uid: String) val (unitWeights, unitIntercept) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta = 0d, - fitIntercept = false, - seed = getSeed + fitIntercept = false ) // join weights @@ -80,3 +88,5 @@ class SyntheticControlEstimator(override val uid: String) .setParent(this) } } + +object SyntheticControlEstimator extends ComplexParamsReadable[SyntheticControlEstimator] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index a2f7a47112..9007939509 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -34,8 +34,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInD // fit time weights val (timeWeights, timeIntercept) = fitTimeWeights( - handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), - seed = getSeed + handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt) ) // fit unit weights @@ -43,8 +42,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInD val (unitWeights, unitIntercept) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta, - fitIntercept = true, - seed = getSeed + fitIntercept = true ) // join weights diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 137cd4f860..d721d0d63f 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -25,7 +25,7 @@ trait SyntheticEstimator { private[causal] val weightsCol = "weights" private[causal] val epsilon = 1E-10 - private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, seed: Long): (DVector, Double) = { + private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean): (DVector, Double) = { val size = matrixOps.size(A) if (size._1 * size._2 <= getLocalSolverThreshold) { // If matrix size is less than LocalSolverThreshold (defaults to 1M), @@ -39,24 +39,24 @@ trait SyntheticEstimator { val bzb = convertToBDV(b.collect()) // b is assumed to be non-sparse val solver = new ConstrainedLeastSquare[BDM[Double], BDV[Double]]( step = this.getStepSize, maxIter = this.getMaxIter, - numIterNoChange = getNumIterNoChange, tol = this.getTol + numIterNoChange = get(numIterNoChange), tol = this.getTol ) - val (x, intercept) = solver.solve(bzA, bzb, lambda, fitIntercept, seed) + val (x, intercept) = solver.solve(bzA, bzb, lambda, fitIntercept) val xdf = A.sparkSession.createDataset[VectorEntry](x.mapPairs((i, v) => VectorEntry(i, v)).toArray.toSeq) (xdf, intercept) } else { implicit val cacheOps: CacheOps[DVector] = DVectorCacheOps val solver = new ConstrainedLeastSquare[DMatrix, DVector]( step = this.getStepSize, maxIter = this.getMaxIter, - numIterNoChange = getNumIterNoChange, tol = this.getTol + numIterNoChange = get(numIterNoChange), tol = this.getTol ) - solver.solve(A, b, lambda, fitIntercept, seed) + solver.solve(A, b, lambda, fitIntercept) } } - private[causal] def fitTimeWeights(indexedControlDf: DataFrame, seed: Long = Random.nextLong): (DVector, Double) = { + private[causal] def fitTimeWeights(indexedControlDf: DataFrame): (DVector, Double) = { val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache val outcomePre = indexedPreControl @@ -67,7 +67,7 @@ trait SyntheticEstimator { .agg(avg(col(getOutcomeCol)).as("value")) .as[VectorEntry] - solveCLS(outcomePre, outcomePostMean, lambda = 0d, fitIntercept = true, seed) + solveCLS(outcomePre, outcomePostMean, lambda = 0d, fitIntercept = true) } private[causal] def calculateRegularization(data: DataFrame): Double = { @@ -89,8 +89,7 @@ trait SyntheticEstimator { private[causal] def fitUnitWeights(indexedPreDf: DataFrame, zeta: Double, - fitIntercept: Boolean, - seed: Long = Random.nextLong): (DVector, Double) = { + fitIntercept: Boolean): (DVector, Double) = { val outcomePreControl = indexedPreDf.filter(not(treatment)) @@ -106,7 +105,7 @@ trait SyntheticEstimator { zeta * zeta * t_pre } - val (weights, intercept) = solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept, seed) + val (weights, intercept) = solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept) (weights, intercept) } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala index a66b8e6b0a..a6d2ed5cd9 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala @@ -26,22 +26,10 @@ trait SyntheticEstimatorParams extends Params final val numIterNoChange = new IntParam(this, "numIterNoChange", "Early termination when number of iterations without change reached.", ParamValidators.gt(0)) - def getNumIterNoChange: Option[Int] = get(numIterNoChange) + def getNumIterNoChange: Int = $(numIterNoChange) def setNumIterNoChange(value: Int): this.type = set(numIterNoChange, value) - /** - * Param for creating the randomized initial solution to the constrained least square problem. - * @group expertParam - */ - final val seed = new LongParam(this, "seed", "seed for randomized initial solution") - - /** @group expertGetParam */ - def getSeed: Long = $(seed) - - /** @group expertGetParam */ - def setSeed(value: Long): this.type = set(seed, value) - /** * Param for deciding whether to collect part of data on driver node and solve the constrained least square problems * locally on driver. @@ -49,7 +37,7 @@ trait SyntheticEstimatorParams extends Params */ final val localSolverThreshold = new LongParam(this, "localSolverThreshold", "threshold for using local solver on driver node. Local solver is faster but relies on part of data " + - "being collected on driver node.") + "being collected on driver node.", ParamValidators.gt(0)) /** @group expertGetParam */ def getLocalSolverThreshold: Long = $(localSolverThreshold) @@ -68,7 +56,6 @@ trait SyntheticEstimatorParams extends Params tol -> 1E-3, maxIter -> 100, handleMissingOutcome -> "zero", - seed -> Random.nextLong, localSolverThreshold -> 1000 * 1000 ) } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala index 755920e108..6cf9602bb0 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala @@ -2,12 +2,8 @@ package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{norm, DenseVector => BDV, max => bmax, sum => bsum} import breeze.numerics.{abs => babs, exp => bexp} -import breeze.stats.distributions.RandBasis import breeze.stats.{mean => bmean} -import org.apache.spark.mllib.random.RandomRDDs.uniformRDD -import org.apache.spark.sql.functions.{ - coalesce, col, lit, exp => sexp, mean => smean, sum => ssum, max => smax, abs => sabs -} +import org.apache.spark.sql.functions.{coalesce, col, lit, abs => sabs, exp => sexp, max => smax, mean => smean, sum => ssum} import org.apache.spark.sql.{Encoder, Encoders, Row, SparkSession} case class VectorEntry(i: Long, value: Double) @@ -37,7 +33,7 @@ trait VectorOps[T] { def dot(vec1: T, vec2: T): Double - def uniformRandom(size: Long, seed: Long = util.Random.nextLong): T + def make(size: Long, value: => Double): T } object DVectorOps extends VectorOps[DVector] { @@ -92,21 +88,26 @@ object DVectorOps extends VectorOps[DVector] { .toDVector(col("l.i"), col("l.value") * col("r.value")) } - def uniformRandom(size: Long, seed: Long = util.Random.nextLong): DVector = { + def make(size: Long, value: => Double): DVector = { val spark = SparkSession.active import spark.implicits._ - val rdd = uniformRDD(spark.sparkContext, size, seed = seed).zipWithIndex.map(t => t.swap) - rdd.toDF("i", "value").as[VectorEntry] + val data = 0L until size + spark + .sparkContext + .parallelize(data) + .toDF("i") + .withColumn("value", lit(value)) + .as[VectorEntry] } - override def exp(vec: DVector): DVector = { + def exp(vec: DVector): DVector = { vec.toDVector( col("i"), sexp(col("value")) ) } - override def center(vec: DVector): DVector = { + def center(vec: DVector): DVector = { val m = mean(vec) vec.toDVector( col("i"), @@ -114,12 +115,12 @@ object DVectorOps extends VectorOps[DVector] { ) } - override def mean(vec: DVector): Double = { + def mean(vec: DVector): Double = { val Row(mean: Double) = vec.agg(smean(col("value"))).head mean } - override def dot(vec1: DVector, vec2: DVector): Double = { + def dot(vec1: DVector, vec2: DVector): Double = { val Row(dot: Double) = vec1.as("l") .join(vec2.as("r"), col("l.i") === col("r.i"), "inner") .agg(ssum(col("l.value") * col("r.value")).as("value")) @@ -132,46 +133,45 @@ object BzVectorOps extends VectorOps[BDV[Double]] { /** * sqrt( x'*x ) */ - override def nrm2(vector: BDV[Double]): Double = norm(vector, 2) + def nrm2(vector: BDV[Double]): Double = norm(vector, 2) /** * alpha * x + y */ - override def axpy(x: BDV[Double], yOpt: Option[BDV[Double]], alpha: Double = 1d): BDV[Double] = { + def axpy(x: BDV[Double], yOpt: Option[BDV[Double]], alpha: Double = 1d): BDV[Double] = { val ax = x * alpha yOpt.map(_ + ax).getOrElse(ax) } - override def maxAbs(vec: BDV[Double]): Double = { + def maxAbs(vec: BDV[Double]): Double = { bmax(babs(vec)) } - override def sum(vec: BDV[Double]): Double = { + def sum(vec: BDV[Double]): Double = { bsum(vec) } - override def elementwiseProduct(vec1: BDV[Double], vec2: BDV[Double]): BDV[Double] = { + def elementwiseProduct(vec1: BDV[Double], vec2: BDV[Double]): BDV[Double] = { vec1 *:* vec2 } - override def uniformRandom(size: Long, seed: Long = util.Random.nextLong): BDV[Double] = { - val r = RandBasis.withSeed(seed.toInt).uniform - BDV.rand(size.toInt, r) + def make(size: Long, value: => Double): BDV[Double] = { + BDV.fill(size.toInt)(value) } - override def exp(vec: BDV[Double]): BDV[Double] = { + def exp(vec: BDV[Double]): BDV[Double] = { bexp(vec) } - override def center(vec: BDV[Double]): BDV[Double] = { + def center(vec: BDV[Double]): BDV[Double] = { vec - bmean(vec) } - override def mean(vec: BDV[Double]): Double = { + def mean(vec: BDV[Double]): Double = { bmean(vec) } - override def dot(vec1: BDV[Double], vec2: BDV[Double]): Double = { + def dot(vec1: BDV[Double], vec2: BDV[Double]): Double = { vec1 dot vec2 } } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala index 12419fa6be..bd25330458 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -54,8 +54,7 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, def solve(A: TMat, b: TVec, lambda: Double = 0d, - fitIntercept: Boolean = false, - seed: Long = util.Random.nextLong): (TVec, Double) = { + fitIntercept: Boolean = false): (TVec, Double) = { val aCentered = if (fitIntercept) matrixOps.centerColumns(A) else A val bCentered = if (fitIntercept) vectorOps.center(b) else b @@ -65,7 +64,7 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, val lossFunc = getLossFunc(aCentered, bCentered, lambda) val md = new MirrorDescent[TVec](lossFunc, step, maxIter, numIterNoChange, tol) - val init = vectorOps.uniformRandom(xSize, seed) + val init = vectorOps.make(xSize, 1d / xSize) val x = md.solve(init) if (fitIntercept){ diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala new file mode 100644 index 0000000000..53702f5dea --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala @@ -0,0 +1,97 @@ +package com.microsoft.azure.synapse.ml.causal + +import breeze.linalg.sum +import breeze.stats.distributions.RandBasis +import com.microsoft.azure.synapse.ml.causal.linalg.{DVector, DVectorOps, VectorOps} +import com.microsoft.azure.synapse.ml.core.test.fuzzing.{EstimatorFuzzing, TestObject} +import org.apache.log4j.{Level, Logger} +import org.apache.spark.ml.util.MLReadable +import org.scalactic.{Equality, TolerantNumerics} + +class VerifySyntheticControlEstimator + extends EstimatorFuzzing[SyntheticControlEstimator]{ + import spark.implicits._ + + spark.sparkContext.setLogLevel("INFO") + Logger.getLogger("org").setLevel(Level.WARN) + Logger.getLogger("akka").setLevel(Level.WARN) + + private lazy val rb = RandBasis.withSeed(47) + private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + + private lazy val data = { + val rand1 = rb.gaussian(100, 2) + val controlPre1 = for { + unit <- 1 to 99 + time <- 1 to 10 + } yield (unit, time, 0, 0, rand1.sample) + + val rand2 = rb.gaussian(50, 2) + val controlPre2 = for { + unit <- 100 to 100 + time <- 1 to 10 + } yield (unit, time, 0, 0, rand2.sample) + + val rand3 = rb.gaussian(90, 2) + val controlPost1 = for { + unit <- 1 to 99 + time <- 11 to 20 + } yield (unit, time, 0, 1, rand3.sample) + + val rand4 = rb.gaussian(30, 2) + val controlPost2 = for { + unit <- 100 to 100 + time <- 11 to 20 + } yield (unit, time, 0, 1, rand4.sample) + + val rand5 = rb.gaussian(50, 2) + val treatPre = for { + unit <- 101 to 200 + time <- 1 to 10 + } yield (unit, time, 1, 0, rand5.sample) + + val rand6 = rb.gaussian(40, 2) + val treatPost = for { + unit <- 101 to 200 + time <- 11 to 20 + } yield (unit, time, 1, 1, rand6.sample) + + controlPre1 ++ controlPre2 ++ controlPost1 ++ controlPost2 ++ treatPre ++ treatPost + } + + private lazy val df = data.toDF("Unit", "Time", "treatment", "postTreatment", "outcome") + + private lazy val estimator = new SyntheticControlEstimator() + .setTreatmentCol("treatment") + .setPostTreatmentCol("postTreatment") + .setOutcomeCol("outcome") + .setUnitCol("Unit") + .setTimeCol("Time") + .setMaxIter(500) + // Set LocalSolverThreshold to 1 to force Spark mode + // Spark mode and breeze mode should get same loss history and same solution + // .setLocalSolverThreshold(1) + + test("SyntheticControlEstimator can estimate the treatment effect") { + val summary = estimator.fit(df).getSummary + + assert(summary.unitIntercept.get === 0d) + + implicit val VectorOps: VectorOps[DVector] = DVectorOps + val unitWeights = summary.unitWeights.get.toBreeze + assert(sum(unitWeights) === 1d) + assert(unitWeights.size === 100) + // Almost all weights go to the last unit since all other units are intentionally placed far away from treatment units. + assert(math.abs(unitWeights(99) - 1.0) < 0.01) + assert(summary.treatmentEffect === 10.524771996841912) + assert(summary.standardError === 0.25634367410218883) + } + + override def testObjects(): Seq[TestObject[SyntheticControlEstimator]] = Seq( + new TestObject(estimator, df, df) + ) + + override def reader: MLReadable[_] = SyntheticControlEstimator + + override def modelReader: MLReadable[_] = DiffInDiffModel +} diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala index 5754035e1a..768bab89b3 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala @@ -64,9 +64,9 @@ class VerifyVectorOps extends TestBase { test("VectorOps.uniformRandom computes correctly") { implicit val VectorOps: VectorOps[DVector] = DVectorOps - val result1 = DVectorOps.uniformRandom(20, 47).toBreeze - val result2 = BzVectorOps.uniformRandom(20, 47) - assert(result1.length === result2.length) + val result1 = DVectorOps.make(20, 1d/20).toBreeze + val result2 = BzVectorOps.make(20, 1d/20) + assert(result1 === result2) assert(result1.forall(0 <= _ && _ <= 1)) assert(result2.forall(0 <= _ && _ <= 1)) } diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala index a64d56a735..ab198aa2d6 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala @@ -5,6 +5,7 @@ import breeze.linalg.{sum, DenseMatrix => BDM, DenseVector => BDV} import com.microsoft.azure.synapse.ml.causal._ import com.microsoft.azure.synapse.ml.causal.linalg._ import com.microsoft.azure.synapse.ml.core.test.base.TestBase +import org.apache.log4j.{Level, Logger} import org.scalactic.{Equality, TolerantNumerics} class VerifyConstrainedLeastSquare extends TestBase { @@ -15,20 +16,43 @@ class VerifyConstrainedLeastSquare extends TestBase { private implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + spark.sparkContext.setLogLevel("INFO") + Logger.getLogger("org").setLevel(Level.WARN) + Logger.getLogger("akka").setLevel(Level.WARN) + test("Fit CLS without intercept") { - val cls = new ConstrainedLeastSquare(1.5, 1000) - val solution = cls.solve(matrixA, vectorB, seed = 47) + val cls = new ConstrainedLeastSquare(0.5, 1000) + val solution = cls.solve(matrixA, vectorB) assert(solution._2 === 0d) assert(sum(solution._1) === 1.0) assert(solution._1.forall(0 <= _ && _ <= 1)) val lossFunc = cls.getLossFunc(matrixA, vectorB, 0d) - assert(lossFunc(solution._1) === 6.0854513873062155) + assert(lossFunc(solution._1) === 6.087082814745372) + } + + test("Fit CLS without intercept distributed") { + val mA = matrixA.toDMatrix + val vB = vectorB.toDVector + + implicit val dMatrixOps: MatrixOps[DMatrix, DVector] = DMatrixOps + implicit val dVectorOps: VectorOps[DVector] = DVectorOps + implicit val dCacheOps: CacheOps[DVector] = DVectorCacheOps + + val cls = new ConstrainedLeastSquare[DMatrix, DVector](0.5, 10) + + val solution = cls.solve(mA, vB) + assert(solution._2 === 0d) + val weights = solution._1.toBreeze + assert(sum(weights) === 1.0) + assert(weights.forall(0 <= _ && _ <= 1)) + val lossFunc = cls.getLossFunc(mA, vB, 0d) + assert(lossFunc(solution._1) === 6.672597868965775) } test("Fit CLS with intercept") { val cls = new ConstrainedLeastSquare(1.5, 1000) - val solution = cls.solve(matrixA, vectorB, fitIntercept = true, seed = 47) - assert(solution._2 === 0.025653849372480997) + val solution = cls.solve(matrixA, vectorB, fitIntercept = true) + assert(solution._2 === 0.025647456560875026) assert(sum(solution._1) === 1.0) assert(solution._1.forall(0 <= _ && _ <= 1)) @@ -39,16 +63,16 @@ class VerifyConstrainedLeastSquare extends TestBase { ) val loss = math.pow(vectorOps.nrm2(error), 2) - assert(loss === 6.027842402810429) + assert(loss === 6.027794680382836) } test("Fit CLS with L2 regularization") { val cls = new ConstrainedLeastSquare(1.5, 1000) - val solution = cls.solve(matrixA, vectorB, lambda = 1.5, seed = 47) + val solution = cls.solve(matrixA, vectorB, lambda = 1.5) assert(solution._2 === 0d) assert(sum(solution._1) === 1.0) assert(solution._1.forall(0 <= _ && _ <= 1)) val lossFunc = cls.getLossFunc(matrixA, vectorB, 1.5) - assert(lossFunc(solution._1) === 6.219637309765119) + assert(lossFunc(solution._1) === 6.219635205398321) } } diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala index de89111a03..8728d4a66c 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala @@ -38,11 +38,11 @@ class VerifyMirrorDescent extends TestBase { implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps val md = new MirrorDescent(loss, 0.5, 1000) - val initial = BDV.rand[Double](50, RandBasis.withSeed(47).uniform) + val initial = BDV.fill(50, 1d/50) val solution = md.solve(initial) assert(sum(solution) === 1.0) assert(solution.forall(0 <= _ && _ <= 1)) - assert(loss(solution) === 6.087454460148611) + assert(loss(solution) === 6.087082814745372) val lossHistory = md.history.map(_.valueAt) assert(lossHistory.length === 1001) } @@ -50,12 +50,12 @@ class VerifyMirrorDescent extends TestBase { test("MirrorDescent finds constrained solution with early stopping") { implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps val md = new MirrorDescent(loss, 0.5, 1000, Some(5), tol = 1E-2) - val initial = BDV.rand[Double](50, RandBasis.withSeed(47).uniform) + val initial = BDV.fill(50, 1d/50) val solution = md.solve(initial) assert(sum(solution) === 1.0) assert(solution.forall(0 <= _ && _ <= 1)) - assert(loss(solution) === 6.1839598830745794) + assert(loss(solution) === 6.186123997006668) val lossHistory = md.history.map(_.valueAt) - assert(lossHistory.length === 59) + assert(lossHistory.length === 55) } } From 2c7a6a0d4e63d957a157aba5c0b66e6c8ffa20db Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 6 Oct 2023 19:19:44 -0700 Subject: [PATCH 09/41] unit test for SyntheticDiffInDiffEstimator --- .../ml/causal/BaseDiffInDiffEstimator.scala | 2 +- .../ml/causal/SyntheticControlEstimator.scala | 11 +- .../causal/SyntheticDiffInDiffEstimator.scala | 28 +++-- .../ml/causal/SyntheticEstimator.scala | 18 +-- .../VerifySyntheticDiffInDiffEstimator.scala | 107 ++++++++++++++++++ 5 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 30054aebff..8c299c5873 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -20,7 +20,7 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) s"Column ${field.name} must be numeric type or boolean type, but got $dataType instead.") } - private def validateFieldNumericType(field: StructField): Unit = { + protected def validateFieldNumericType(field: StructField): Unit = { val dataType = field.dataType require(dataType.isInstanceOf[NumericType], s"Column ${field.name} must be numeric type, but got $dataType instead.") diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 671869e34e..4e738ab01d 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -1,7 +1,6 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable -import com.microsoft.azure.synapse.ml.logging.SynapseMLLogging import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.functions._ @@ -11,17 +10,15 @@ import org.apache.spark.sql.{Dataset, Row} class SyntheticControlEstimator(override val uid: String) extends BaseDiffInDiffEstimator(uid) with SyntheticEstimator - with DiffInDiffEstimatorParams with SyntheticEstimatorParams with ComplexParamsWritable - with Wrappable - with SynapseMLLogging { + with Wrappable { import SyntheticEstimator._ - def this() = this(Identifiable.randomUID("sc")) + def this() = this(Identifiable.randomUID("syncon")) - override def fit(dataset: Dataset[_]): DiffInDiffModel = { + override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ val df = dataset .withColumn(getTreatmentCol, treatment.cast(BooleanType)) .withColumn(getPostTreatmentCol, postTreatment.cast(BooleanType)) @@ -86,7 +83,7 @@ class SyntheticControlEstimator(override val uid: String) copyValues(new DiffInDiffModel(this.uid)) .setSummary(Some(summary)) .setParent(this) - } + }, dataset.columns.length) } object SyntheticControlEstimator extends ComplexParamsReadable[SyntheticControlEstimator] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 9007939509..87b3bfdfee 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -1,20 +1,28 @@ package com.microsoft.azure.synapse.ml.causal +import com.microsoft.azure.synapse.ml.codegen.Wrappable +import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.functions._ -import org.apache.spark.sql.types.IntegerType +import org.apache.spark.sql.types.{BooleanType, IntegerType} import org.apache.spark.sql.{Dataset, Row} -class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInDiffEstimator(uid) - with SyntheticEstimator - with SyntheticEstimatorParams { +class SyntheticDiffInDiffEstimator(override val uid: String) + extends BaseDiffInDiffEstimator(uid) + with SyntheticEstimator + with SyntheticEstimatorParams + with ComplexParamsWritable + with Wrappable { import SyntheticEstimator._ - def this() = this(Identifiable.randomUID("sdid")) + def this() = this(Identifiable.randomUID("syndid")) - override def fit(dataset: Dataset[_]): DiffInDiffModel = { - val df = dataset.toDF + override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ + val df = dataset + .withColumn(getTreatmentCol, treatment.cast(BooleanType)) + .withColumn(getPostTreatmentCol, postTreatment.cast(BooleanType)) + .toDF val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache val controlPreDf = controlDf.filter(not(postTreatment)).cache @@ -92,5 +100,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInD copyValues(new DiffInDiffModel(this.uid)) .setSummary(Some(summary)) .setParent(this) - } -} \ No newline at end of file + }, dataset.columns.length) +} + +object SyntheticDiffInDiffEstimator extends ComplexParamsReadable[SyntheticDiffInDiffEstimator] \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index d721d0d63f..09718d8079 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -7,10 +7,10 @@ import org.apache.spark.sql.types._ import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} import com.microsoft.azure.synapse.ml.causal.linalg._ import com.microsoft.azure.synapse.ml.causal.opt.ConstrainedLeastSquare -import scala.util.Random +import com.microsoft.azure.synapse.ml.logging.SynapseMLLogging -trait SyntheticEstimator { - this: DiffInDiffEstimatorParams with SyntheticEstimatorParams => +trait SyntheticEstimator extends SynapseMLLogging { + this: BaseDiffInDiffEstimator with SyntheticEstimatorParams => import SyntheticEstimator._ @@ -56,7 +56,7 @@ trait SyntheticEstimator { } } - private[causal] def fitTimeWeights(indexedControlDf: DataFrame): (DVector, Double) = { + private[causal] def fitTimeWeights(indexedControlDf: DataFrame): (DVector, Double) = logVerb("fitTimeWeights", { val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache val outcomePre = indexedPreControl @@ -68,9 +68,9 @@ trait SyntheticEstimator { .as[VectorEntry] solveCLS(outcomePre, outcomePostMean, lambda = 0d, fitIntercept = true) - } + }) - private[causal] def calculateRegularization(data: DataFrame): Double = { + private[causal] def calculateRegularization(data: DataFrame): Double = logVerb("calculateRegularization", { val Row(firstDiffStd: Double) = data .filter(not(treatment) and not(postTreatment)) .select( @@ -85,11 +85,11 @@ trait SyntheticEstimator { val nTreatedPost = data.filter(treatment and postTreatment).count val zeta = math.pow(nTreatedPost, 0.25) * firstDiffStd zeta - } + }) private[causal] def fitUnitWeights(indexedPreDf: DataFrame, zeta: Double, - fitIntercept: Boolean): (DVector, Double) = { + fitIntercept: Boolean): (DVector, Double) = logVerb("fitUnitWeights", { val outcomePreControl = indexedPreDf.filter(not(treatment)) @@ -107,7 +107,7 @@ trait SyntheticEstimator { val (weights, intercept) = solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept) (weights, intercept) - } + }) private[causal] def handleMissingOutcomes(indexed: DataFrame, maxTimeLength: Int): DataFrame = { // "skip", "zero", "impute" diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala new file mode 100644 index 0000000000..e059dac6f8 --- /dev/null +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala @@ -0,0 +1,107 @@ +package com.microsoft.azure.synapse.ml.causal + +import breeze.linalg.sum +import breeze.stats.distributions.RandBasis +import com.microsoft.azure.synapse.ml.core.test.fuzzing.{EstimatorFuzzing, TestObject} +import org.apache.log4j.{Level, Logger} +import org.apache.spark.ml.util.MLReadable +import org.scalactic.{Equality, TolerantNumerics} +import com.microsoft.azure.synapse.ml.causal.linalg._ + +class VerifySyntheticDiffInDiffEstimator + extends EstimatorFuzzing[SyntheticDiffInDiffEstimator] { + + import spark.implicits._ + + spark.sparkContext.setLogLevel("INFO") + Logger.getLogger("org").setLevel(Level.WARN) + Logger.getLogger("akka").setLevel(Level.WARN) + + private lazy val rb = RandBasis.withSeed(47) + private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + + private lazy val data = { + + // 101 - 110 + val rand1 = rb.gaussian(100, 1) + val controlPre1 = for { + unit <- 1 to 99 + time <- 1 to 10 + } yield (unit, time, 0, 0, rand1.sample + time) + + // 51 - 60 + val rand2 = rb.gaussian(50, 1) + val controlPre2 = for { + unit <- 100 to 100 + time <- 1 to 10 + } yield (unit, time, 0, 0, rand2.sample + time) + + val rand3 = rb.gaussian(110, 1) + val controlPost1 = for { + unit <- 1 to 99 + time <- 11 to 20 + } yield (unit, time, 0, 1, rand3.sample) + + val rand4 = rb.gaussian(60, 1) + val controlPost2 = for { + unit <- 100 to 100 + time <- 11 to 20 + } yield (unit, time, 0, 1, rand4.sample) + + val rand5 = rb.gaussian(50, 1) + val treatPre = for { + unit <- 101 to 200 + time <- 1 to 10 + } yield (unit, time, 1, 0, rand5.sample) + + val rand6 = rb.gaussian(40, 1) + val treatPost = for { + unit <- 101 to 200 + time <- 11 to 20 + } yield (unit, time, 1, 1, rand6.sample) + + controlPre1 ++ controlPre2 ++ controlPost1 ++ controlPost2 ++ treatPre ++ treatPost + } + + private lazy val df = data.toDF("Unit", "Time", "treatment", "postTreatment", "outcome") + + private lazy val estimator = new SyntheticDiffInDiffEstimator() + .setTreatmentCol("treatment") + .setPostTreatmentCol("postTreatment") + .setOutcomeCol("outcome") + .setUnitCol("Unit") + .setTimeCol("Time") + .setMaxIter(500) + // Set LocalSolverThreshold to 1 to force Spark mode + // Spark mode and breeze mode should get same loss history and same solution + // .setLocalSolverThreshold(1) + + test("SyntheticDiffInDiffEstimator can estimate the treatment effect") { + implicit val VectorOps: VectorOps[DVector] = DVectorOps + + val summary = estimator.fit(df).getSummary + assert(summary.timeIntercept.get === 4.945419871092341) + + val timeWeights = summary.timeWeights.get.toBreeze + assert(sum(timeWeights) === 1.0) + assert(timeWeights.size === 10) + assert(timeWeights.forall(0 <= _ && _ <= 1)) + + assert(summary.unitIntercept.get === -54.712303815108314) + val unitWeights = summary.unitWeights.get.toBreeze + assert(sum(unitWeights) === 1.0) + assert(unitWeights.size === 100) + assert(unitWeights.forall(0 <= _ && _ <= 1)) + + assert(summary.treatmentEffect === -14.92249952070377) + assert(summary.standardError === 0.28948753364970986) + } + + override def testObjects(): Seq[TestObject[SyntheticDiffInDiffEstimator]] = Seq( + new TestObject(estimator, df, df) + ) + + override def reader: MLReadable[_] = SyntheticControlEstimator + + override def modelReader: MLReadable[_] = DiffInDiffModel +} From 697b5783df2f9bbf21f3273bc2162de844845544 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 6 Oct 2023 19:23:07 -0700 Subject: [PATCH 10/41] logClass --- .../microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala | 2 ++ .../azure/synapse/ml/causal/SyntheticControlEstimator.scala | 2 ++ .../azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala | 2 ++ 3 files changed, 6 insertions(+) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index 472f3e1afe..2fb78719bd 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -14,6 +14,8 @@ class DiffInDiffEstimator(override val uid: String) with Wrappable with SynapseMLLogging { + logClass() + def this() = this(Identifiable.randomUID("did")) override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 4e738ab01d..929b207f6d 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -14,6 +14,8 @@ class SyntheticControlEstimator(override val uid: String) with ComplexParamsWritable with Wrappable { + logClass() + import SyntheticEstimator._ def this() = this(Identifiable.randomUID("syncon")) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 87b3bfdfee..d93dcd5c4d 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -14,6 +14,8 @@ class SyntheticDiffInDiffEstimator(override val uid: String) with ComplexParamsWritable with Wrappable { + logClass() + import SyntheticEstimator._ def this() = this(Identifiable.randomUID("syndid")) From 0aa9fc252bcd42d0dbe8ec5704bdce33ae6ec2bd Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sat, 7 Oct 2023 17:09:34 -0700 Subject: [PATCH 11/41] Python code gen Signed-off-by: Jason Wang --- .../python/synapse/ml/causal/DiffInDiffModel.py | 15 +++++++++++++++ .../ml/causal/BaseDiffInDiffEstimator.scala | 4 ++++ .../synapse/ml/causal/SyntheticEstimator.scala | 6 +++++- .../azure/synapse/ml/codegen/Wrappable.scala | 2 +- 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 core/src/main/python/synapse/ml/causal/DiffInDiffModel.py diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py new file mode 100644 index 0000000000..1398c96f67 --- /dev/null +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -0,0 +1,15 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE in project root for information. + +import sys + +if sys.version >= "3": + basestring = str + +from synapse.ml.causal._DiffInDiffModel import _DiffInDiffModel +from pyspark.ml.common import inherit_doc +import numpy as np + +@inherit_doc +class DiffInDiffModel(_DiffInDiffModel): + pass diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 8c299c5873..b29fb09bed 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -1,6 +1,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.DVector +import com.microsoft.azure.synapse.ml.codegen.Wrappable import org.apache.spark.SparkException import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.ml.param.ParamMap @@ -63,8 +64,11 @@ case class DiffInDiffSummary(treatmentEffect: Double, standardError: Double, class DiffInDiffModel(override val uid: String) extends Model[DiffInDiffModel] + with Wrappable with ComplexParamsWritable { + override protected lazy val pyInternalWrapper = true + def this() = this(Identifiable.randomUID("did")) private final var summary: Option[DiffInDiffSummary] = None diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 09718d8079..1896850d72 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -214,7 +214,7 @@ object SyntheticEstimator { } private[causal] def assignRowIndex(df: DataFrame, colName: String): DataFrame = { - df.sqlContext.createDataFrame( + df.sparkSession.createDataFrame( df.rdd.zipWithIndex.map(element => Row.fromSeq(Seq(element._2) ++ element._1.toSeq) ), @@ -225,6 +225,10 @@ object SyntheticEstimator { } private[causal] def createIndex(data: DataFrame, inputCol: String, indexCol: String): DataFrame = { + // The orderBy operation is needed here when creating index for Time. When fitting unit weights, + // we need to compute the regularization term zeta, which depends on the first order difference of + // the outcome, ordered by time. If we don't order the data by time when creating the time index, + // the regularization term will be incorrect. assignRowIndex(data.select(col(inputCol)).distinct.orderBy(col(inputCol)), indexCol) } } \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/codegen/Wrappable.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/codegen/Wrappable.scala index a4be8bafcb..e8ea1bda28 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/codegen/Wrappable.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/codegen/Wrappable.scala @@ -33,7 +33,7 @@ trait BaseWrappable extends Params { .find(c => Set("Estimator", "ProbabilisticClassifier", "Predictor", "BaseRegressor", "Ranker")( c.getSuperclass.getSimpleName)) .get - val typeArgs = thisStage.getClass.getGenericSuperclass.asInstanceOf[ParameterizedType].getActualTypeArguments + val typeArgs = superClass.getGenericSuperclass.asInstanceOf[ParameterizedType].getActualTypeArguments val modelTypeArg = superClass.getSuperclass.getSimpleName match { case "Estimator" => typeArgs.head From 5660acefbcc3a5c3e15fb42825633baf830079a7 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sun, 8 Oct 2023 22:52:14 -0700 Subject: [PATCH 12/41] pyspark wrapper --- .../synapse/ml/causal/DiffInDiffModel.py | 21 ++++++++- .../ml/causal/BaseDiffInDiffEstimator.scala | 47 ++++++++++++++++++- .../ml/causal/SyntheticControlEstimator.scala | 2 + .../causal/SyntheticDiffInDiffEstimator.scala | 2 + 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 1398c96f67..7130cbd440 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -8,8 +8,27 @@ from synapse.ml.causal._DiffInDiffModel import _DiffInDiffModel from pyspark.ml.common import inherit_doc +from pyspark.sql import SparkSession, DataFrame import numpy as np @inherit_doc class DiffInDiffModel(_DiffInDiffModel): - pass + + @staticmethod + def _mapOption(option, func): + return func(option.get()) if option.isDefined() else None + + @staticmethod + def _unwrapOption(option): + return DiffInDiffModel._mapOption(option, lambda x: x) + + def __init__(self, java_obj=None) -> None: + super(DiffInDiffModel, self).__init__(java_obj = java_obj) + # self.summary = DiffInDiffSummary(javaSummary=java_obj.getSummary()) + self.summary = java_obj.getSummary() + self.treatmentEffect = self.summary.treatmentEffect() + self.standardError = self.summary.standardError() + self.timeIntercept = DiffInDiffModel._unwrapOption(self.summary.timeIntercept()) + self.unitIntercept = DiffInDiffModel._unwrapOption(self.summary.unitIntercept()) + self.timeWeights = DiffInDiffModel._mapOption(java_obj.getTimeWeights(), lambda x: DataFrame(x, SparkSession.getActiveSession())) + self.unitWeights = DiffInDiffModel._mapOption(java_obj.getUnitWeights(), lambda x: DataFrame(x, SparkSession.getActiveSession())) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index b29fb09bed..5971605df2 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -2,6 +2,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.DVector import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.param.DataFrameParam import org.apache.spark.SparkException import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.ml.param.ParamMap @@ -59,14 +60,26 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) } case class DiffInDiffSummary(treatmentEffect: Double, standardError: Double, - timeWeights: Option[DVector] = None, timeIntercept: Option[Double] = None, - unitWeights: Option[DVector] = None, unitIntercept: Option[Double] = None) + timeWeights: Option[DVector] = None, + timeIntercept: Option[Double] = None, + unitWeights: Option[DVector] = None, + unitIntercept: Option[Double] = None) class DiffInDiffModel(override val uid: String) extends Model[DiffInDiffModel] + with HasUnitCol + with HasTimeCol with Wrappable with ComplexParamsWritable { + final val timeIndex = new DataFrameParam(this, "timeIndex", "time index") + def getTimeIndex: DataFrame = $(timeIndex) + def setTimeIndex(value: DataFrame): this.type = set(timeIndex, value) + + final val unitIndex = new DataFrameParam(this, "unitIndex", "unit index") + def getUnitIndex: DataFrame = $(unitIndex) + def setUnitIndex(value: DataFrame): this.type = set(unitIndex, value) + override protected lazy val pyInternalWrapper = true def this() = this(Identifiable.randomUID("did")) @@ -92,6 +105,36 @@ class DiffInDiffModel(override val uid: String) override def transform(dataset: Dataset[_]): DataFrame = dataset.toDF override def transformSchema(schema: StructType): StructType = schema + + def getTimeWeights: Option[DataFrame] = { + (get(timeIndex), getSummary.timeWeights) match { + case (Some(idxDf), Some(timeWeights)) => + Some( + idxDf.join(timeWeights, idxDf(SyntheticEstimator.TimeIdxCol) === timeWeights("i"), "left_outer") + .select( + idxDf(getTimeCol), + timeWeights("value") + ) + ) + case _ => + None + } + } + + def getUnitWeights: Option[DataFrame] = { + (get(unitIndex), getSummary.unitWeights) match { + case (Some(idxDf), Some(unitWeights)) => + Some( + idxDf.join(unitWeights, idxDf(SyntheticEstimator.UnitIdxCol) === unitWeights("i"), "left_outer") + .select( + idxDf(getUnitCol), + unitWeights("value") + ) + ) + case _ => + None + } + } } object DiffInDiffModel extends ComplexParamsReadable[DiffInDiffModel] \ No newline at end of file diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 929b207f6d..abdf2b0a4c 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -85,6 +85,8 @@ class SyntheticControlEstimator(override val uid: String) copyValues(new DiffInDiffModel(this.uid)) .setSummary(Some(summary)) .setParent(this) + .setTimeIndex(timeIdx) + .setUnitIndex(unitIdx) }, dataset.columns.length) } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index d93dcd5c4d..653bcebc6c 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -102,6 +102,8 @@ class SyntheticDiffInDiffEstimator(override val uid: String) copyValues(new DiffInDiffModel(this.uid)) .setSummary(Some(summary)) .setParent(this) + .setTimeIndex(timeIdx) + .setUnitIndex(unitIdx) }, dataset.columns.length) } From b2d947759a85bfe45681b0c11faa9c1121ae75f0 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 9 Oct 2023 00:15:39 -0700 Subject: [PATCH 13/41] expose loss history --- .../python/synapse/ml/causal/DiffInDiffModel.py | 3 ++- .../ml/causal/BaseDiffInDiffEstimator.scala | 16 +++++++++++++++- .../ml/causal/SyntheticControlEstimator.scala | 9 ++++----- .../ml/causal/SyntheticDiffInDiffEstimator.scala | 14 ++++++++------ .../synapse/ml/causal/SyntheticEstimator.scala | 13 ++++++------- .../ml/causal/opt/ConstrainedLeastSquare.scala | 6 +++--- .../VerifySyntheticDiffInDiffEstimator.scala | 5 +++++ 7 files changed, 43 insertions(+), 23 deletions(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 7130cbd440..3e768d2e5e 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -24,7 +24,6 @@ def _unwrapOption(option): def __init__(self, java_obj=None) -> None: super(DiffInDiffModel, self).__init__(java_obj = java_obj) - # self.summary = DiffInDiffSummary(javaSummary=java_obj.getSummary()) self.summary = java_obj.getSummary() self.treatmentEffect = self.summary.treatmentEffect() self.standardError = self.summary.standardError() @@ -32,3 +31,5 @@ def __init__(self, java_obj=None) -> None: self.unitIntercept = DiffInDiffModel._unwrapOption(self.summary.unitIntercept()) self.timeWeights = DiffInDiffModel._mapOption(java_obj.getTimeWeights(), lambda x: DataFrame(x, SparkSession.getActiveSession())) self.unitWeights = DiffInDiffModel._mapOption(java_obj.getUnitWeights(), lambda x: DataFrame(x, SparkSession.getActiveSession())) + self.lossHistoryTimeWeights = DiffInDiffModel._unwrapOption(self.summary.getLossHistoryTimeWeightsJava()) + self.lossHistoryUnitWeights = DiffInDiffModel._unwrapOption(self.summary.getLossHistoryUnitWeightsJava()) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 5971605df2..704099fb31 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -12,6 +12,8 @@ import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable, Estima import org.apache.spark.sql.types.{BooleanType, NumericType, StructField, StructType} import org.apache.spark.sql.{DataFrame, Dataset} +import java.util + abstract class BaseDiffInDiffEstimator(override val uid: String) extends Estimator[DiffInDiffModel] with DiffInDiffEstimatorParams { @@ -63,7 +65,19 @@ case class DiffInDiffSummary(treatmentEffect: Double, standardError: Double, timeWeights: Option[DVector] = None, timeIntercept: Option[Double] = None, unitWeights: Option[DVector] = None, - unitIntercept: Option[Double] = None) + unitIntercept: Option[Double] = None, + lossHistoryTimeWeights: Option[List[Double]] = None, + lossHistoryUnitWeights: Option[List[Double]] = None) { + import scala.collection.JavaConverters._ + + def getLossHistoryTimeWeightsJava: Option[util.List[Double]] = { + lossHistoryTimeWeights.map(_.asJava) + } + + def getLossHistoryUnitWeightsJava: Option[util.List[Double]] = { + lossHistoryUnitWeights.map(_.asJava) + } +} class DiffInDiffModel(override val uid: String) extends Model[DiffInDiffModel] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index abdf2b0a4c..b1df372f85 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -37,7 +37,7 @@ class SyntheticControlEstimator(override val uid: String) .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) .localCheckpoint(true) - val (unitWeights, unitIntercept) = fitUnitWeights( + val (unitWeights, unitIntercept, lossHistory) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta = 0d, fitIntercept = false @@ -76,10 +76,9 @@ class SyntheticControlEstimator(override val uid: String) val summary = DiffInDiffSummary( treatmentEffect, standardError, - None, - None, - Some(unitWeights), - Some(unitIntercept) + unitWeights = Some(unitWeights), + unitIntercept = Some(unitIntercept), + lossHistoryUnitWeights = Some(lossHistory.toList) ) copyValues(new DiffInDiffModel(this.uid)) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 653bcebc6c..b339441340 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -43,13 +43,13 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .localCheckpoint(true) // fit time weights - val (timeWeights, timeIntercept) = fitTimeWeights( + val (timeWeights, timeIntercept, lossHistoryTimeWeights) = fitTimeWeights( handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt) ) // fit unit weights val zeta = calculateRegularization(df) - val (unitWeights, unitIntercept) = fitUnitWeights( + val (unitWeights, unitIntercept, lossHistoryUnitWeights) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta, fitIntercept = true @@ -93,10 +93,12 @@ class SyntheticDiffInDiffEstimator(override val uid: String) val summary = DiffInDiffSummary( treatmentEffect, standardError, - Some(timeWeights), - Some(timeIntercept), - Some(unitWeights), - Some(unitIntercept) + timeWeights = Some(timeWeights), + timeIntercept = Some(timeIntercept), + unitWeights = Some(unitWeights), + unitIntercept = Some(unitIntercept), + lossHistoryTimeWeights = Some(lossHistoryTimeWeights.toList), + lossHistoryUnitWeights = Some(lossHistoryUnitWeights.toList) ) copyValues(new DiffInDiffModel(this.uid)) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 1896850d72..d32487629b 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -25,7 +25,7 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] val weightsCol = "weights" private[causal] val epsilon = 1E-10 - private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean): (DVector, Double) = { + private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean): (DVector, Double, Seq[Double]) = { val size = matrixOps.size(A) if (size._1 * size._2 <= getLocalSolverThreshold) { // If matrix size is less than LocalSolverThreshold (defaults to 1M), @@ -42,9 +42,9 @@ trait SyntheticEstimator extends SynapseMLLogging { numIterNoChange = get(numIterNoChange), tol = this.getTol ) - val (x, intercept) = solver.solve(bzA, bzb, lambda, fitIntercept) + val (x, intercept, lossHistory) = solver.solve(bzA, bzb, lambda, fitIntercept) val xdf = A.sparkSession.createDataset[VectorEntry](x.mapPairs((i, v) => VectorEntry(i, v)).toArray.toSeq) - (xdf, intercept) + (xdf, intercept, lossHistory) } else { implicit val cacheOps: CacheOps[DVector] = DVectorCacheOps val solver = new ConstrainedLeastSquare[DMatrix, DVector]( @@ -56,7 +56,7 @@ trait SyntheticEstimator extends SynapseMLLogging { } } - private[causal] def fitTimeWeights(indexedControlDf: DataFrame): (DVector, Double) = logVerb("fitTimeWeights", { + private[causal] def fitTimeWeights(indexedControlDf: DataFrame): (DVector, Double, Seq[Double]) = logVerb("fitTimeWeights", { val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache val outcomePre = indexedPreControl @@ -89,7 +89,7 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] def fitUnitWeights(indexedPreDf: DataFrame, zeta: Double, - fitIntercept: Boolean): (DVector, Double) = logVerb("fitUnitWeights", { + fitIntercept: Boolean): (DVector, Double, Seq[Double]) = logVerb("fitUnitWeights", { val outcomePreControl = indexedPreDf.filter(not(treatment)) @@ -105,8 +105,7 @@ trait SyntheticEstimator extends SynapseMLLogging { zeta * zeta * t_pre } - val (weights, intercept) = solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept) - (weights, intercept) + solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept) }) private[causal] def handleMissingOutcomes(indexed: DataFrame, maxTimeLength: Int): DataFrame = { diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala index bd25330458..39393a6f1b 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -54,7 +54,7 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, def solve(A: TMat, b: TVec, lambda: Double = 0d, - fitIntercept: Boolean = false): (TVec, Double) = { + fitIntercept: Boolean = false): (TVec, Double, Seq[Double]) = { val aCentered = if (fitIntercept) matrixOps.centerColumns(A) else A val bCentered = if (fitIntercept) vectorOps.center(b) else b @@ -70,9 +70,9 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, if (fitIntercept){ val colMean = matrixOps.colMean(A) val bMean = vectorOps.mean(b) - (x, bMean - vectorOps.dot(x, colMean)) + (x, bMean - vectorOps.dot(x, colMean), md.history.map(_.valueAt)) } else { - (x, 0d) + (x, 0d, md.history.map(_.valueAt)) } } } \ No newline at end of file diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala index e059dac6f8..8e4ed61578 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala @@ -95,6 +95,11 @@ class VerifySyntheticDiffInDiffEstimator assert(summary.treatmentEffect === -14.92249952070377) assert(summary.standardError === 0.28948753364970986) + + import scala.collection.JavaConverters._ + + scala.collection.JavaConverters.seqAsJavaList(summary.lossHistoryTimeWeights.get) + } override def testObjects(): Seq[TestObject[SyntheticDiffInDiffEstimator]] = Seq( From ca354ccdcea27e8246d8a04ea5d45dbc34fb8e9b Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 9 Oct 2023 22:40:50 -0700 Subject: [PATCH 14/41] fix bugs for synthetic control --- .../ml/causal/BaseDiffInDiffEstimator.scala | 6 ++--- .../ml/causal/DiffInDiffEstimator.scala | 2 +- .../ml/causal/SyntheticControlEstimator.scala | 27 ++++++++++++++++--- .../causal/SyntheticDiffInDiffEstimator.scala | 7 ++++- .../VerifySyntheticControlEstimator.scala | 4 +-- .../VerifySyntheticDiffInDiffEstimator.scala | 5 ---- 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 704099fb31..84e6a09c55 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -41,9 +41,9 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) private[causal] val interactionCol = "interaction" - private[causal] def fitLinearModel(df: DataFrame, weightCol: Option[String] = None) = { + private[causal] def fitLinearModel(df: DataFrame, featureCols: Array[String], fitIntercept: Boolean, weightCol: Option[String] = None) = { val assembler = new VectorAssembler() - .setInputCols(Array(getPostTreatmentCol, getTreatmentCol, interactionCol)) + .setInputCols(featureCols) .setOutputCol("features") val regression = weightCol @@ -53,7 +53,7 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) regression .setFeaturesCol("features") .setLabelCol(getOutcomeCol) - .setFitIntercept(true) + .setFitIntercept(fitIntercept) .setLoss("squaredError") .setRegParam(0.0) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index 2fb78719bd..4665e533a1 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -30,7 +30,7 @@ class DiffInDiffEstimator(override val uid: String) ) .withColumn(interactionCol, treatment * postTreatment) - val linearModel = fitLinearModel(didData) + val linearModel = fitLinearModel(didData, Array(getPostTreatmentCol, getTreatmentCol, interactionCol), fitIntercept = true) val treatmentEffect = linearModel.coefficients(2) val standardError = linearModel.summary.coefficientStandardErrors(2) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index b1df372f85..a026a30e9d 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -1,8 +1,8 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable -import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.ml.util.Identifiable +import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.sql.functions._ import org.apache.spark.sql.types.{BooleanType, IntegerType} import org.apache.spark.sql.{Dataset, Row} @@ -51,6 +51,7 @@ class SyntheticControlEstimator(override val uid: String) val indexedDf = df.join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") val didData = indexedDf.select( + col(getTimeCol), col(UnitIdxCol), postTreatment.cast(IntegerType).as(getPostTreatmentCol), treatment.cast(IntegerType).as(getTreatmentCol), @@ -58,6 +59,8 @@ class SyntheticControlEstimator(override val uid: String) ).as("l") .join(unitWeights.as("u"), col(s"l.$UnitIdxCol") === col("u.i"), "left_outer") .select( + col(getTimeCol), + col(UnitIdxCol), postTreatment, treatment, outcome, @@ -68,10 +71,26 @@ class SyntheticControlEstimator(override val uid: String) (treatment * postTreatment).as(interactionCol) ) - val linearModel = fitLinearModel(didData, Some(weightsCol)) + val meanDf = didData.groupBy(getTimeCol).agg( + mean(interactionCol).as(s"mean_$interactionCol"), + mean(getOutcomeCol).as(s"mean_$getOutcomeCol") + ) + + val demeaned = didData.as("l").join(meanDf.as("r"), + col(s"l.$getTimeCol") === col(s"r.$getTimeCol"), "inner") + .select( + col(s"l.$getTimeCol").as(getTimeCol), + postTreatment, + treatment, + (col(interactionCol) - col(s"mean_$interactionCol")).as(interactionCol), + (col(getOutcomeCol) - col(s"mean_$getOutcomeCol")).as(getOutcomeCol), + col(weightsCol) + ) + + val linearModel = fitLinearModel(demeaned, Array(interactionCol), fitIntercept = false, Some(weightsCol)) - val treatmentEffect = linearModel.coefficients(2) - val standardError = linearModel.summary.coefficientStandardErrors(2) + val treatmentEffect = linearModel.coefficients(0) + val standardError = linearModel.summary.coefficientStandardErrors(0) val summary = DiffInDiffSummary( treatmentEffect, diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index b339441340..7067880a85 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -85,7 +85,12 @@ class SyntheticDiffInDiffEstimator(override val uid: String) (treatment * postTreatment).as(interactionCol) ) - val linearModel = fitLinearModel(didData, Some(weightsCol)) + val linearModel = fitLinearModel( + didData, + Array(getPostTreatmentCol, getTreatmentCol, interactionCol), + fitIntercept = true, + Some(weightsCol) + ) val treatmentEffect = linearModel.coefficients(2) val standardError = linearModel.summary.coefficientStandardErrors(2) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala index 53702f5dea..c86ece3914 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala @@ -83,8 +83,8 @@ class VerifySyntheticControlEstimator assert(unitWeights.size === 100) // Almost all weights go to the last unit since all other units are intentionally placed far away from treatment units. assert(math.abs(unitWeights(99) - 1.0) < 0.01) - assert(summary.treatmentEffect === 10.524771996841912) - assert(summary.standardError === 0.25634367410218883) + assert(summary.treatmentEffect === 10.428249543876486) + assert(summary.standardError === 1.2392998896347176) } override def testObjects(): Seq[TestObject[SyntheticControlEstimator]] = Seq( diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala index 8e4ed61578..e059dac6f8 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala @@ -95,11 +95,6 @@ class VerifySyntheticDiffInDiffEstimator assert(summary.treatmentEffect === -14.92249952070377) assert(summary.standardError === 0.28948753364970986) - - import scala.collection.JavaConverters._ - - scala.collection.JavaConverters.seqAsJavaList(summary.lossHistoryTimeWeights.get) - } override def testObjects(): Seq[TestObject[SyntheticDiffInDiffEstimator]] = Seq( From b02aa6f9e42cfe16d3c667661166a6a16c95d925 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 11 Oct 2023 17:17:32 -0700 Subject: [PATCH 15/41] fix time effects for synthetic control estimator --- .../ml/causal/SyntheticControlEstimator.scala | 45 +++++++++++-------- .../causal/SyntheticDiffInDiffEstimator.scala | 4 +- .../ml/causal/SyntheticEstimatorParams.scala | 2 +- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index a026a30e9d..a1ac8d6f48 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -1,11 +1,12 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable +import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer} import org.apache.spark.ml.util.Identifiable -import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} +import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable, Pipeline} import org.apache.spark.sql.functions._ import org.apache.spark.sql.types.{BooleanType, IntegerType} -import org.apache.spark.sql.{Dataset, Row} +import org.apache.spark.sql.{DataFrame, Dataset, Row} class SyntheticControlEstimator(override val uid: String) extends BaseDiffInDiffEstimator(uid) @@ -60,7 +61,6 @@ class SyntheticControlEstimator(override val uid: String) .join(unitWeights.as("u"), col(s"l.$UnitIdxCol") === col("u.i"), "left_outer") .select( col(getTimeCol), - col(UnitIdxCol), postTreatment, treatment, outcome, @@ -71,23 +71,14 @@ class SyntheticControlEstimator(override val uid: String) (treatment * postTreatment).as(interactionCol) ) - val meanDf = didData.groupBy(getTimeCol).agg( - mean(interactionCol).as(s"mean_$interactionCol"), - mean(getOutcomeCol).as(s"mean_$getOutcomeCol") - ) + val timeEncoded = encodeTimeEffect(didData) - val demeaned = didData.as("l").join(meanDf.as("r"), - col(s"l.$getTimeCol") === col(s"r.$getTimeCol"), "inner") - .select( - col(s"l.$getTimeCol").as(getTimeCol), - postTreatment, - treatment, - (col(interactionCol) - col(s"mean_$interactionCol")).as(interactionCol), - (col(getOutcomeCol) - col(s"mean_$getOutcomeCol")).as(getOutcomeCol), - col(weightsCol) - ) - - val linearModel = fitLinearModel(demeaned, Array(interactionCol), fitIntercept = false, Some(weightsCol)) + val linearModel = fitLinearModel( + timeEncoded, + Array(interactionCol, getTimeCol + "_enc"), + fitIntercept = true, + Some(weightsCol) + ) val treatmentEffect = linearModel.coefficients(0) val standardError = linearModel.summary.coefficientStandardErrors(0) @@ -106,6 +97,22 @@ class SyntheticControlEstimator(override val uid: String) .setTimeIndex(timeIdx) .setUnitIndex(unitIdx) }, dataset.columns.length) + + private def encodeTimeEffect(didData: DataFrame): DataFrame = { + val stringIndexer = new StringIndexer() + .setInputCol(getTimeCol) + .setOutputCol(getTimeCol + "idx") + val oneHotEncoder = new OneHotEncoder() + .setInputCol(getTimeCol + "idx") + .setOutputCol(getTimeCol + "_enc") + .setDropLast(false) + + val timeEncoded = new Pipeline() + .setStages(Array(stringIndexer, oneHotEncoder)) + .fit(didData) + .transform(didData) + timeEncoded + } } object SyntheticControlEstimator extends ComplexParamsReadable[SyntheticControlEstimator] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 7067880a85..06d1561cd8 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -61,10 +61,10 @@ class SyntheticDiffInDiffEstimator(override val uid: String) countDistinct(when(treatment, col(getUnitCol))), ).head - val indexed_df = df.join(timeIdx, df(getTimeCol) === timeIdx(getTimeCol), "left_outer") + val indexedDf = df.join(timeIdx, df(getTimeCol) === timeIdx(getTimeCol), "left_outer") .join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") - val didData = indexed_df.select( + val didData = indexedDf.select( col(UnitIdxCol), col(TimeIdxCol), postTreatment.cast(IntegerType).as(getPostTreatmentCol), diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala index a6d2ed5cd9..fb756dbb09 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala @@ -52,7 +52,7 @@ trait SyntheticEstimatorParams extends Params def setTol(value: Double): this.type = set(tol, value) setDefault( - stepSize -> 0.5, + stepSize -> 1.0, tol -> 1E-3, maxIter -> 100, handleMissingOutcome -> "zero", From e55a8ac1b7a703f767c9246e9cb0102142ed17dc Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 11 Oct 2023 17:49:01 -0700 Subject: [PATCH 16/41] fix unit test --- .../synapse/ml/causal/VerifySyntheticControlEstimator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala index c86ece3914..066a9f2c86 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala @@ -83,8 +83,8 @@ class VerifySyntheticControlEstimator assert(unitWeights.size === 100) // Almost all weights go to the last unit since all other units are intentionally placed far away from treatment units. assert(math.abs(unitWeights(99) - 1.0) < 0.01) - assert(summary.treatmentEffect === 10.428249543876486) - assert(summary.standardError === 1.2392998896347176) + assert(summary.treatmentEffect === 10.402769542126478) + assert(summary.standardError === 0.1808473095549071) } override def testObjects(): Seq[TestObject[SyntheticControlEstimator]] = Seq( From b76641ba009ccfdf9738aeb0180b7d645546afbb Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 12 Oct 2023 02:05:35 -0700 Subject: [PATCH 17/41] add notebook --- .../ml/causal/SyntheticControlEstimator.scala | 4 +- ... Synthetic difference in differences.ipynb | 7517 +++++++++++++++++ 2 files changed, 7519 insertions(+), 2 deletions(-) create mode 100644 docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index a1ac8d6f48..746dc9bafb 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -102,16 +102,16 @@ class SyntheticControlEstimator(override val uid: String) val stringIndexer = new StringIndexer() .setInputCol(getTimeCol) .setOutputCol(getTimeCol + "idx") + val oneHotEncoder = new OneHotEncoder() .setInputCol(getTimeCol + "idx") .setOutputCol(getTimeCol + "_enc") .setDropLast(false) - val timeEncoded = new Pipeline() + new Pipeline() .setStages(Array(stringIndexer, oneHotEncoder)) .fit(didData) .transform(didData) - timeEncoded } } diff --git a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb new file mode 100644 index 0000000000..cd0f9aa46e --- /dev/null +++ b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb @@ -0,0 +1,7517 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Scalable Synthetic Difference in Differences" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sample notebook aims to show readers how to use SynapseML's `DiffInDiffEstimator`, `SyntheticControlEstimator` and `SyntheticDiffInDiffEstimator` to estimate the causal effect of a treatment on a particular outcome." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this sample notebook, we will use the California smoking cessation program example to demonstrate the usage. The goal of the analysis is to estimate the effect of increased cigarette taxes on smoking in California." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "from pyspark.sql.types import *\n", + "from synapse.ml.causal import DiffInDiffEstimator, SyntheticControlEstimator, SyntheticDiffInDiffEstimator\n", + "from matplotlib import pyplot as plt\n", + "from matplotlib import style\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "spark.sparkContext.setLogLevel(\"INFO\")\n", + "style.use(\"ggplot\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will select 5 columns from the dataset: state, year, cigsale, california, after_treatment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "df = spark.read.option(\"header\", True).option(\"inferSchema\", True).csv(\"wasbs://publicwasb@mmlspark.blob.core.windows.net/smoking.csv\") \\\n", + " .select(\"state\", \"year\", \"cigsale\", \"california\", \"after_treatment\")\n", + "display(df)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we use the `DiffInDiffEstimator` to estimate the causal effect with regular difference in differences method. We set the treatment indicator column to \"california\", set post-treatment indicator column to \"after_treatment\", and set the outcome column to \"cigsale\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "estimator1 = DiffInDiffEstimator(treatmentCol=\"california\", postTreatmentCol=\"after_treatment\", outcomeCol = \"cigsale\")\n", + "model1 = estimator1.fit(df)\n", + "\n", + "print(\"[Diff in Diff] treatment effect: {}\".format(model1.treatmentEffect))\n", + "print(\"[Diff in Diff] standard error: {}\".format(model1.standardError))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The treatment effect estimated by difference in differences should be -27.349." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we use `SyntheticControlEstimator` to synthesize a control unit and use the synthetic control to estimate the causal effect. To create the synthetic control unit, we need to set the column which indicates the time when each outcome is measured, and the column which indicates the unit for which the outcome is measured." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "estimator2 = SyntheticControlEstimator(\n", + " timeCol = \"year\", unitCol = \"state\", treatmentCol = \"california\", postTreatmentCol = \"after_treatment\", outcomeCol = \"cigsale\", \n", + " maxIter = 5000, numIterNoChange = 50, tol = 1E-4, stepSize = 1.0)\n", + "\n", + "model2 = estimator2.fit(df)\n", + "\n", + "print(\"[Synthetic Control] treatment effect: {}\".format(model2.treatmentEffect))\n", + "print(\"[Synthetic Control] standard error: {}\".format(model2.standardError))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The treatment effect estimated by synthetic control should be about -19.354." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Internally, a constrained least square regression is used to solve the unit weights for the synthetic control, and we can plot the loss history." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "lossHistory = pd.Series(np.array(model2.lossHistoryUnitWeights))\n", + "\n", + "plt.plot(lossHistory[2000:])\n", + "plt.title('loss history - unit weights')\n", + "plt.xlabel('Iteration')\n", + "plt.ylabel('Loss')\n", + "plt.show()\n", + "\n", + "print(\"Mimimal loss: {}\".format(lossHistory.min()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also visualize the synthetic control and compare it with the treated unit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "sc_weights = model2.unitWeights.toPandas().set_index(\"state\")\n", + "pdf = df.toPandas()\n", + "sc = pdf.query(\"~california\").pivot(index=\"year\", columns=\"state\", values=\"cigsale\").dot(sc_weights)\n", + "\n", + "plt.plot(sc, label=\"Synthetic Control\")\n", + "plt.plot(sc.index, pdf.query(\"california\")[\"cigsale\"], label=\"California\", color=\"C1\")\n", + "\n", + "plt.title(\"Synthetic Control Estimation\")\n", + "plt.ylabel(\"Cigarette Sales\")\n", + "plt.vlines(x=1988, ymin=40, ymax=140, linestyle=\":\", lw=2, label=\"Proposition 99\", color=\"black\")\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly, we use `SyntheticDiffInDiffEstimator` to estimate the causal effect." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "estimator3 = SyntheticDiffInDiffEstimator(\n", + " timeCol = \"year\", unitCol = \"state\", treatmentCol = \"california\", postTreatmentCol = \"after_treatment\", outcomeCol = \"cigsale\", \n", + " maxIter = 5000, numIterNoChange = 50, tol = 1E-4, stepSize = 1.0)\n", + "\n", + "model3 = estimator3.fit(df)\n", + "\n", + "print(\"[Synthetic Diff in Diff] treatment effect: {}\".format(model3.treatmentEffect))\n", + "print(\"[Synthetic Diff in Diff] standard error: {}\".format(model3.standardError))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The treatment effect estimated by synthetic control should be about -15.554." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, we can plot the loss history from the optimizer used to solve the unit weights and the time weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "lossHistory = pd.Series(np.array(model3.lossHistoryUnitWeights))\n", + "\n", + "plt.plot(lossHistory[1000:])\n", + "plt.title('loss history - unit weights')\n", + "plt.xlabel('Iteration')\n", + "plt.ylabel('Loss')\n", + "plt.show()\n", + "\n", + "print(\"Mimimal loss: {}\".format(lossHistory.min()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "lossHistory = pd.Series(np.array(model3.lossHistoryTimeWeights))\n", + "\n", + "plt.plot(lossHistory[1000:])\n", + "plt.title('loss history - time weights')\n", + "plt.xlabel('Iteration')\n", + "plt.ylabel('Loss')\n", + "plt.show()\n", + "\n", + "print(\"Mimimal loss: {}\".format(lossHistory.min()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we plot the synthetic diff in diff estimate together with the time weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [], + "source": [ + "unit_weights = model3.unitWeights.toPandas().set_index(\"state\")\n", + "unit_intercept = model3.unitIntercept\n", + "\n", + "time_weights = model3.timeWeights.toPandas().set_index(\"year\")\n", + "time_intercept = model3.timeIntercept\n", + "\n", + "pdf = df.toPandas()\n", + "pivot_df_control = pdf.query(\"~california\").pivot(index='year', columns='state', values='cigsale')\n", + "pivot_df_treat = pdf.query(\"california\").pivot(index='year', columns='state', values='cigsale')\n", + "sc_did = pivot_df_control.values @ unit_weights.values\n", + "treated_mean = pivot_df_treat.mean(axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "jupyter": { + "outputs_hidden": false, + "source_hidden": false + }, + "nteract": { + "transient": { + "deleting": false + } + } + }, + "outputs": [ + { + "data": { + "application/vnd.livy.statement-meta+json": { + "execution_finish_time": "2023-10-12T00:10:00.2809196Z", + "execution_start_time": "2023-10-12T00:09:59.110083Z", + "livy_statement_state": "available", + "parent_msg_id": "391a813b-98ff-49a4-b247-6f3aa60587f1", + "queued_time": "2023-10-12T00:07:18.6147362Z", + "session_id": "42", + "session_start_time": null, + "spark_jobs": null, + "spark_pool": "jasowang33", + "state": "finished", + "statement_id": 15 + }, + "text/plain": [ + "StatementMeta(jasowang33, 42, 15, Finished, Available)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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S8Yc8DXuuOMOAN59910sFkvs+OjRo7noooti35eXl3PXXXexbNkyNBoN8+fPZ9GiRaSlpQHw2GOPsWTJEq6++moeffRRnE4nCxYs4Mknn8RoNHLjjTeyfPlyli9fzt///ncAVqxYQWlpKRdccAEvvvgijzzyCJs3b+bFF19k+vTpLFq0iLfeegu3282kSZO49957mTJlygE9vuXLl+Pz+bj99tt55ZVXWLFiBbNnz8blcjF16lT+9re/sWDBgtj49957j1/84hesW7cOq9XKqlWruOOOO9i5cyejR4/mhhtu4IorruCDDz5gwoQJB/x8i/gg00iFEKIfRVSVquYAbv/Bv+MohBBCCCHEYNTY2Minn37Kj3/84w5BW5uEhAQg2m12+eWX09TUxOuvv85LL73Enj17uOaaazqM37NnDx988AH//Oc/+ec//8mKFSt46qmnALj//vs56qijuOSSS1i7di1r164lOzs7dt9Fixbx61//ms8++4yxY8fywAMP8N577/Hkk0+yZMkShg8fziWXXEJjY+MBPcaXXnqJs88+G71ez1lnncVLL70EgMPhYOHChfznP//pMP6NN97g5JNPxmq14na7+fGPf8yYMWNYsmQJt912Gw888MABXV/EJ+lsE0KIfuAJhllV5uar0mbqWkLoV9SQaID8RAPDEo0MSzCSZTeg1SgDXaoQQgghhBD9Yvfu3aiqyqhRo3oc98UXX7B582aWL19OTk4OEF0HbcGCBXz77bexbrNIJMITTzyBzWYD4LzzzuOLL77gtttuw+FwYDAYMJlMpKend7rGbbfdxty5cwHweDwsXryYJ554goULFwLw6KOPMnv2bP797393Cvm609zczHvvvcdbb70FwLnnnsvZZ5/NokWLsNvtnHvuudxwww14vV7MZjPNzc188skn/PWvfwXgP//5D4qi8Oijj2IymSgqKqKqqorbbrutV9cX8UvCNiGE6EPlrgBf7nGxuqKFYCS6yoWuNVCrbQlS0xLkm/IWAPQahdyE1vCtNYBLMGlRFAnghBBCCCHE4Keq0d+H9/f77fbt28nOzo4FbQBFRUUkJCSwffv2WNiWl5cXC9oA0tPTqa2t7VUtkyZNin29e/dugsEgM2bMiB3T6/VMmTKF7du39+p8EO1SGzZsGOPHjwdgwoQJDBs2jLfeeotLL72U448/Hp1O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Zszg8ssvZ9KkSbHbTjzxRH7/+9/zxhtvcN555/XbcyBhm+gzgXCELbXRgG1TjQdvaO/i3ha9hvHpZiZlWClKNaHXSsAmhBCDWZJZx5w8O3Py7IQjKnua/NHwrc5LqTNAeXP045NdLoxahVEpptiU07Z1OIUQQggx9F111VUEg0GefPJJhg0bRm1tLcuWLaOpqWmgSzskSUlJh3T/devWceGFFzJ69GgeeeQRRo0ahdvt5sMPP+T+++/n9ddf76NKeyccDqMoSp90e7333ntYLBZmzZoVO3b77bfz7bffsmjRIoqKimhsbOSbb76hsbGxw30vueQSbr31VoLBIFVVVSxZsoRrr72W73//+/zud7/r8bqPP/44CxYswO/3s2vXLv71r39xxhln8Nhjj3HBBRfExl144YX84x//kLBNxC9vMMKmmugOoptrvQQjewM2u1HLxAwLkzIsjEw2oZWuBiGEGJK0GoURySZGJJs4rSiJZn+YbXXR6abb6rw0ByJsrPGyscYLQJpVx+hUM2NTzYxMMWGQN2CEEEKIA6aqKoFw593L+5tB2/vXdU6nk5UrV/Laa68xZ84cAHJzc5k6dWpszM0330xdXR2LFy+OHQuFQkyfPp1f/epXXHTRRZx//vmMHTsWo9HISy+9hF6v54c//CG33HILQCzUueKKK2LX+Prrr2Pne+2113j00UdxOp0sWLCARx99FJvNBkSfx6effpoXXniBmpoaCgoKuPHGGznjjDMoLS2NhTTjxo0D4IILLuDJJ5/sNI3U7/fz4IMP8uabb1JfX092djbXXXcdP/jBDzo9L6qqctNNN1FQUMAbb7zRIeCaMGFC7HEAbN68mbvvvps1a9ZgMpk4/fTTueeee7BarQDceOONuFwuZs6cyTPPPEMgEOCcc87hnnvuQa/Xc/7551NWVsa9997LvffeC0B5eXlsOuhTTz3FAw88wK5du1i2bBkOh4O7776bjz/+GL/fz5w5c7j//vsZMWJEr//c33rrLU466aQOxz7++GPuu+8+jj/+eADy8vI6dJy1MZlMpKenA9FutaOOOopRo0Zx8803c8YZZzB37txur5uQkBC7b15eHvPmzeOGG27grrvu4sQTTyQxMRGAk046id/85jfs2bOHYcOG9fpxHQgJ28QBc/vDbKjxsL7aw/Y6L+3/f08266IBW6aFYYlGWSxbCCGOQHajlqNybByVYyOiqlS4ArGut+JGP7UtIWpbmlm2pxmdRmFE0t6NFjJsetl1WgghhOiFQFjl1x+VHPbrPnRiPoZejrVarVitVpYsWcK0adMwGo2dxvzgBz/gvPPOo7q6moyMDAA++eQTWlpa+N73vhcb9+qrr/LTn/6Ud955h9WrV3PTTTcxY8YMjj/+eN577z0mTZoU62zSaveuHbtnzx4++OAD/vnPf+J0OvnZz37Gn/70J371q18B8Mgjj/D+++/z0EMPUVBQwIoVK/jFL35BSkoKM2fO5Nlnn+Wqq65i6dKl2O12TCZTl4/1uuuuY9WqVfz2t79l3LhxlJSU0NDQ0OXYjRs3snXrVv785z932UnWNl3V6/Vy6aWXMm3aNN59913q6uq47bbbuPPOO3nyySdj47/66ivS09N59dVXKS4u5tprr2Xs2LFccsklPPvss5x44olccsklXHLJJR2u4/V6+dOf/sSjjz5KUlISqamp/PznP6e4uJh//OMf2Gw2HnzwQX74wx/y2Wef9Xra7MqVKznnnHM6HEtLS+OTTz7htNNOiwWdvXXBBRdw//338/777/cYtnXlqquu4rXXXmPp0qWceeaZQDSMTU1N5euvv5awTQys+pYAS3c7WV/VQnGjn3YNbGTY9EzKsDAx00KO3SAvkoQQQsRoFIXcBCO5CUZOGJmINxhhe300eNta56XBG2ZbvY9t9T7eppFEk5bRqWbGpJkpTDFhNcivKkIIIcRgpdPpeOKJJ/jlL3/Jv/71LyZMmMDs2bM566yzYp1iM2bMYOTIkbz++utce+21QHQR/jPOOCPWvQUwduxYbr75ZgBGjBjB888/z7Jlyzj++ONJSUkBOnY2tYlEIjzxxBOxgOe8885j2bJlQHQdsWeffZaXX345tn7csGHDWLVqFf/617+YM2dOrBsqNTW1w5pt7e3cuZO33nqLl156KRYG9RTi7Nq1C4BRo0b1+Pz95z//wefz8Yc//AGLxQLAokWL+PGPf8ydd95JWlpa7HE/8MADaLVaRo0axQknnMCyZcu45JJLSEpKQqvVYrPZOj03wWCQBx98kPHjx8fq+vDDD3nzzTeZMWMGAH/84x+ZMWMGS5Ys6RB+dsfpdOJ0OsnMzOxw/He/+x3XXXcdEyZMYNy4ccyYMYMzzjgjdp2eaDQaCgoKKCsr2+/YfbU9x/veNzMz86DO11vyG6zoUYM3xOJva6lqKSMYDNKWseU5DEzMtDAxw0KGrbfvawghhDjSmfUaJmVamZRpRVVValqCbK3zsaXOy456H02+MF+Xufm6zI1GgfxEI8cUwRh7GKteppsKIYQQbQxahYdOzB+Q6x6I008/neOPP56VK1eyevVqPv30U55++mkeffRRLrzwQiDa3fbiiy9y7bXXUldXx//+9z9efvnlDucZO3Zsh+/T09Opq6vb7/Xz8vI6dFKlp6dTX18PwLZt2/D5fJ2megaDQSZMmNDrx7hx40a0Wm1squz+qGrvpv9u376dsWPHxoI2iIaTkUiEnTt3xsK2oqKiDt18GRkZbNy4cb/nNxgMsdATYMeOHeh0OqZNmxY7lpyczMiRI9mxY0evavb5fACduhhnz57N8uXLWbNmDatWreLLL7/knHPO4ZZbbuGmm27q1bkPRttzvW9TkMlkwuv19tt1JWwTPXIYtdS6g6DRUpBkZGKGhQkZFlncWoheenFdLe9sbSQU2YIG0GkUNBoFrQJaRUGrUdBqWr9WWr/WKGiU6JizxiYzO8/e5bmf/aYafyjSeo6O59Qo0WtpFQVN6/l1GoWU6ggBTzP5CQaGJ3XdAi/E4aIoChk2Axk2A3OHOwiGI+xs8LO1db23aneQPY1+KtaUoUZCzMqxMa/AIT+DhBBCCKI/R426wTGryGQyMXfuXObOnctNN93ErbfeymOPPRYL284//3weeughvvnmG1avXk1ubm6HxfUh2iXXnqIoRCKR/V67p/u1fV68eHGnTiyDofdNJd1NLe3OyJEjgWi41VOop6pqtzPH2h/fd3qnoii9CvRMJlOH83R3n96GgxDdOEJRFJxOZ6fb9Ho9s2bNYtasWVx33XU8+eSTPPnkk/z85z/v8fkOh8MUFxcf1A6tbSFhXl5eh+NNTU2xjsj+IGGb6JFOo/DjaelMHpWHt6nugP6RCSFgeKIRb3DvLwH+A1zEdl5BqNvbPit24g7s/xeMjqoBOHtsMj/pJmx75ItyKpsDWA1arHoNFr0Gq0Hb+lmDVa/F0vrZatBg0UfHWQ0a2WlYHBK9VhNduy3NzFlAozfEljov6+pCbK9ysqykma9Km5mcaWV+gYO8hM7rvgghhBAi/hUWFrJkyZLY98nJyZx88sm88sorrF69OhbCHQi9Xk84HD6g+xQVFWE0GikvL++2K60tyOrp3GPHjiUSibB8+fJerSk2fvx4ioqKeOaZZzjzzDM7rdvmdDpJSEigqKiI1157DY/HE+tuW7VqFRqN5oA2LOjtc1NYWEgoFGLNmjWx6Z0NDQ3s2rWLwsLCXl3LYDBQVFTEtm3bmDdvXo9ji4qKCIVC+P3+HsO2V199laamJk477bRe1dDes88+i91u57jjjosd8/l87NmzJzZ9tj9I2Cb2qyjVTKJZj7dpoCsRYvCZlWcn2ayjwdt9aNYTbQ9rIIYPNGdrp6fpeCVNfspcgYM678mjErl2VmaXt324o4kGb6g1mNMywmcgRQlhN2i7HC9EklnH0fkOzp2ZyRcbivlkVxNb6nysrWxhbWULRSkmFoxIoCjFJOuFCiGEEHGooaGBq6++mosuuoixY8dis9lYt24dTz/9NCeffHKHsRdffDGXXXYZ4XA4tgPogcjNzWXZsmXMmDEDg8EQW2utJzabjauvvpp7772XSCTCzJkzcbvdfPPNN1gsFr7//e+Tm5uLoih8/PHHHH/88ZhMpg5ryUG0a+rCCy/klltuiW2QUFZWRl1dXWxR/vYUReHxxx/noosu4txzz+X6669n1KhRtLS08NFHH7F06VJef/11zj33XB577DFuuOEGbrnlFurr6/nNb37DeeedF5tC2ht5eXl8/fXXnHXWWRiNRpKTk7scN2LECE4++WR++ctf8sgjj2C1WnnooYfIzMzs9OfVk3nz5rFy5Uquuuqq2LHzzz+fs846i8mTJ5OUlMS2bdt4+OGHOfroo7Hb987k8fl81NTUEAqFqKqq4v333+fZZ5/lRz/6Ecccc0yP13U6ndTU1BAIBNi1axcvvPACH3zwAX/4wx86rLe3Zs0aDAZDbJ2+/hB3YdumTZt4++23KS4uprGxkVtvvZWZM2d2Ofavf/0rH3/8MZdddhmnn3567HgwGOSFF17gyy+/JBAIMGHCBK688sp+bREUQhx5QhGVr0ubeXdbI+ePT2FadudddXQahVMKE/m/9ftfT6IrWk0PYdshdJpaDN2HbZ7gwad4ph6mMny808nWuvbrIlQCkOswMDbNzLh0C2PTzGTKbpRiH4qiUJhqZlSKiXJXgM+KnaytbIltrJBjN7BghIPJmdYe/80IIYQQ4vCyWq1MmzaNZ599lj179hAMBsnOzubiiy/m+uuv7zD2uOOOIz09naKiok5TOnvj7rvv5r777uP//u//yMzM5Ouvv+7V/X75y1+SmprKn/70J0pKSnA4HEycODFWX1ZWFrfccgsPPfQQN998M+eff36HnUDb/O53v+O3v/0td9xxB42NjWRnZ/OLX/yi2+tOnTqV9957j6eeeopf/vKXNDQ0kJ6ezvTp07nvvvsAMJvNvPjii9x9992cfvrpmEwmTj/9dO65554Dem5uvfVWbr/9do455hj8fj/l5eXdjn388ce5++67ueyyywgEAsyePZsXXnih1zuRAlxyySWcfPLJuFyuWA4zb948Xn31VR5++GF8Ph8ZGRmccMIJ3HjjjR3u++KLL/Liiy9iMBhISkpi4sSJPP3005x66qn7vW7bBhomk4nMzExmzJjBu+++y8SJEzuMe/PNNznnnHMwm829fkwHSlHjbF7g2rVr2bp1KwUFBTz22GPdhm0rV67k1VdfxeVyceaZZ3YI25599llWr17Ntddei91uZ/Hixbjdbh555JEut9XtSW1tLcFg8JAf12CmKApZWVlUVlYO+DRSqSV+6ziSamn0hvhgRxMfbG+KdazNyLFy1/y8Lsc3B8IEDA7q6+sJhyOE1WhQF1FVwpFoaBaOqIRVWj/vPT4pw0JuN1Pl/rm2hmC44/guz9P6dUQFNDqaPD4unpTK0fmOLs/7/X9vPeDprm1+MCmViyamdnnbdf/dRalz/x1ziSYtY9PMjE2zMCbNzIgkE/oDXIj3QBwpf28HYx091dLgDfHFbhcrSptjf1+TzVrmDk9gVq4No67vpzQPhuflSK1DapFaBmMdUkv81xIvdbTV4vV6D3htsP6i1+v7/HWy1+tl2rRpPPbYYwc0XbA/ajkY8VIHxEctV199NRMmTODmm28e8Fraq6+vZ+7cubz//vvk53e/wYjL5cLh6Px6Sa/X96qrMO4626ZOncrUqVN7HNPQ0MBzzz3HnXfeycMPP9zhNo/HwyeffML111/PpEmTALj++uu55pprWL9+PVOmTOnynMFgsMNfAEVRYinnkd5h0fb44+F5kFrit472NQzFWlRVZXOtl3e3NrK81EVon+avb8pbqG4JktnF7rwJJj2ZmQlUabx9+ovaj6dlHNB4RVHIzMykqqqqxzpuOTaHlkAYTzBCSyBMSzCCp/VzSyCCJximJRChpfVzKLL3XFaDttvnvKWX68s1+cIsL3WzvNQNRHe8+t6YZC6bmr6fex6cofz3drDX0b6GfWtJseg5e1wKJ41K5MuSZr7Y46LRG+atzQ18uKOJY4Y5OG6YA7ux76YpD4bn5Uito30NUktHUkv81tG+Bqmlo3ipJV7qiJca+kskEqGmpoZnnnkGu93OSSedNNAliT7wm9/8hg8//HCgy+ikpKSEBx98sMegrc2h/LuLu7BtfyKRCH/84x8588wzO+0mAbBr1y7C4XAsaIPoYov5+fls27at27DtjTfe4LXXXot9X1BQwCOPPHJA86CHuoNp5e0vUktn8VIHDK1afMEwSzZX88qaMrbXursdpwJfV4f5WWFWv9XSV/ZXxzlZ3T+GrvhDYdz+MG5/iASTjkRL14ubLihyUd/ip9kfotkfYk+DB/++qWUXAmGVjOQEsrqpq8kTIMF86FNP4+XPB+KnlnipA3quZeQwuGhOhGW76nh/YxXVzX4+39PCl2UejhuZyqnjMsl09F0nwGB5Xg6neKkDpJbuSC2dxUsdILV0J15qiZc6du3adUBT+fpbX9VSUlLC9OnTyc7O5qmnnjqoqX3x8rzESx0w8LUUFBRw9dVXx0Ut7c2cObPbpcraMxgM3b7+6I1BF7a99dZbaLXabufrNjU1odPpsNk6rp2UkJBAU1NTt+c955xzOOOMM2Lft71oq62tJRQ6uIXNh4redsNILUd2HUOtlsrmAO9va+SjnU377cgam2bm9NFJzMkzUVlZ2ee19JX+rsMAeP3g7bzLNwCXTdzbhq0oCilp6Xy1aTebajxsrvWyqdaD09f1Lkm5plCXz62qqvzote1oNUqHdd8Kkky9XrsrXv584qmWeKnjQGsZa4fRs1L5rtrDJzudlDr9fLixgg83VjAp08rCEQnkJx78DqaD9Xk5EuqQWqSWwViH1BL/tcRLHW21AHEzHa8vpylmZWV1WEPsQM8bD1Mm46kOkFr6QiAQ6PL1h06nG5zTSHuya9cu3nvvPR555JED7mDY33+Oer2+27R1oP9jjReqqsbNcyG1xG8dMHhriagqaytaeHdbI2sqWujpXgatwrzhDk4rSmJE8t6OmZ6uFS/PS7zUoddqKEo1U5hi4qyx0bqq3MFo8NYawJW5AmgVGJVs6rLmclcApz8a0H1Z0syXJc1AdKOGolRzNIBLs1CUasKi73k6Ybw8LxA/tcRLHdD7WhRgUoaFielmdjX6+WSXk821XtZVtbCuqoWRyUYWFiQwJs180N2Qg/F5OVLqAKmlO1JL/NYBUkt34qWWeKlDCHF4Hcq/+0EVtm3evBmXy8W1114bOxaJRFi8eDHvvfcef/7zn0lMTCQUCuF2uzt0t7lcLkaPHj0QZQshBolwBP64opLGbrqrADJtek4tSuT4EYl9uhaUaF2E2G4gy25g4Yjo1twuf5jSJn+3i91vrvV0edwXUllf5WF9lQeoR6PAsEQj49LMjEmLdr+lWeOnnV30PUVRGJlsYmSyicrm6A6mqyta2NngZ2dDDZk2PQtHJDAly4pOdjAVQgghhBB9aFCFbXPnzu20ZesDDzzA3LlzWbBgAQAjRoxAq9Wyfv16jj76aAAaGxspKSnhkksuOew1CyEGD71W4aTCRF7+rr7TbUdlWzmtKIlp2VY0Q3iB2njjMGoZn2Hp9vadDb5enSeiQnGjn+JGP+9uawIgzaLjtyfkk+3oPK2w1Omn3nNwSwiYdBrGpHW91khlc4Aqd/dt9ApQ4m+gsbEFBRWNoqABNBoFnSYaHnWlJRDGHQijoKDRRM+jVRQUBTSxz9GvNQrRca3HhvKCy22y7AZ+MCmNUwuTWLrHxfKSZqrcQf5vfR3vbWtk7nAHc/Ls/bKDqRBCCCGEOPLEXdjm8/moqqqKfV9TU8Pu3bux2WykpqZit9s7jNfpdCQmJpKdnQ2AxWJh4cKFvPDCC9jtdmw2Gy+88AL5+fkdNk0QQhyZgmGVrXVeJnQT4Jw8KpHXNtQTVsFq0HDCiAROLUoiy971ov9iYP10eganj05iS62XTTVeNtd6qWgO9Oq+Tn+YVEvX3W3vbGnkgx1NB1XTsEQjT51e0OVtn+xy8sqGzmFuR6VdHnUYtbxwfmGXt32+28Uzq6oPpMwYBXjj4tFdhm4tgTDPflNNfoKBvAQjeQkG0qz6QRs4J5p1nDkmmRNGJrC8pJmle5pp8oV5e0sjH+1o4uh8B8cNd+CQrlUhhBBCCHEI4i5s27lzJ/fdd1/s+8WLFwMwb948fv7zn/fqHJdddhlarZYnnniCQCDAhAkTuP3229Fo5B1rIY5U9Z4gH+xo4oPtTbj8Yf7fmSPIsHUO0FIses4Zl0KGTc+84Q7pdIlziqKQ6zCS6zBywshEAJp8IbbURoO3zbUedjb46GrT08IUE3rt4AmNeprpGDnEdWS6624rdUbXPGvPqFXIbQ3e2gK4/AQj6VZ9rzelGGgWvZbjRyYyd3gCayrcfFrspKYlxP92Ofl8t5PpOTbmD08g3SZTjYUQQgghxIGLu7Bt/PjxvPLKK70e/+c//7nTMYPBwOWXX87ll1/el6UJIQYZVVXZUO3h3a0NrChtJtwuj1iyvYnLpqZ3eb8fTtn/7jIifiWadMzOszM7L9oJ7Q9F2FHvi4Vvm+u8tAQijE3rfnpqPOppumfkELK2nvKxEmfnLkF/WGVng6/TFF69RiE3wUCew0heYjSIm5ZljevAWq9VmJVnZ0aujU01Xj7Z5WR3k58VpW6+LnUzIcPCggIHw5O6nr4rhBBCCCFEV+IubBNCiEOhqio1LUHWVnr4aEkJO+pauhz30U4nP5iUikEbv0GA6BtGnYbxGZbWtd9SiKgqZc4Aph5CIKtBQ7L54H5EJvQwBdGs15DS03kVUBQNwXAYVY12rEXUaJhm1nWfih1KY1tPIV6p09/r8wQjamxdPPZEj71wfiHGLh5uMBwBlLjpLNQoChMyLEzIsFDc6OOTXU421nj5rtrDd9UeCpKiO5iO62H9QCGEEEIIIdpI2CaEGNR8rV1LW+u8bK3zsq3O2+Nuom3yHAYavaEup5KKoU2jKOQndt4Uob3LpqZ32/l4KM4dl8K541K6vV1RFLKysqisrDygrcbPGJ3EKYWJhFW1NaSLBs8RFSJEQ7t9w7u2Yz1dZVyaGU8ggVJngFKnn5ZgF/Nxu5Fo0na79tmKUjdPfFVBln3vVNS8BCP5CQayHYYBDcELkkxccZSJaneAT4tdrC53U9zo5++NNWTY9PxgppFh0ugmhBBC9NqNN97Iq6++CkTXXM/OzubUU0/l1ltvxWIZ+DeyamtreeCBB1i6dClOp5PZs2fz29/+lhEjRsTG7N69m9/+9resXLmSQCDA/PnzWbRoEWlpMiNGdE3CNiHEoPXxzib+/HVVr6fQGbUK8wsSOK0oUaaFiSFFq1H6Zb20OfmO2HRcVVVp9IUpdfopdfopaQrEvm4OdA7h8hK6DzRLXX7CKpS5ApS5AixvtyeERoFMm741hIt+zNDasR7iunQHKsNm4KKJqZxamMjS3S6WlzZT4w7yly92MTXDyLnjktFLZ6wQQgjRKwsWLODxxx8nGAyycuVKbr31VjweDw8//HCnscFgEL3+8Kybqqoql19+OXq9nueeew6bzcZf//pXLrroIj777DMsFgsej4eLL76YcePGxZa8evTRR/nxj3/MO++8I2vDiy5J2CaEiFueYJjt9T4mZli63P0wy2boVdCWZddzWlESC0ckYDPILoNCHAxFUUg260g265icaY0dV1UVp78thAvEPo9NM3d7rtIu1oJrE1GhojlIRXOQr8vc0YNfVVCQZOT7E1KYnWc/rLuhJph0fG9MMieMTOSz3S4+L2lhZZmbMleAH09NI6WbHW2FEEKIw8Xj8QBgNptjy0MEAgFCoRBarRaj0dhprMlkioVEwWCQYDCIRqPBZDL1OPZgGQwG0tOjswbOOeccvvrqKz744AMefvhhHnvsMZYsWcIVV1zBH/7wB0pLSyktLaWiooK77rqLZcuWodFoOnWTtd3vJz/5CY8//jiNjY0cf/zxPProoyQkJPSqrl27drFmzRo++eQTRo8eDcBDDz3EpEmTePPNN7n44otZtWoVpaWlfPDBB9jt0TchH3/8ccaPH8+yZcuYO3fuIT03YmiSCFYIERciqkqJ089HO5r404pKfvHfYi5+ZTt3/6+UiuauX5iPSjF1u7i73aBlQWEa9y7M4y/fG8GZY5IlaBOiHyiKQqJJx8QMK6cVJXH1jEwWnZDPJZO7n1ZxIGvBtSlu9PPIFxXc8G4xLl/oUEo+KGa9htOKkvjl8UVYDRrKXQEe/6qSjTWew16LEEII0V5hYSGFhYU0NDTEjj399NMUFhZy1113dRg7adIkCgsLKS8vjx17/vnnKSws5NZbb+0wdtasWRQWFrJ9+/Y+r9lkMhEMBmPf7969m3feeYdnn32WDz/8EIDLL7+cpqYmXn/9dV566SX27NnDNddc0+E8u3fv5u233+b555/nxRdfZOPGjdx55529riMQiL7OaB9IarVaDAYDK1euBMDv96MoCgbD3uVnjEYjGo2GVatWHfiDF0cE6WwTQgyIZn+YbXVetrSus7at3oenm/WgttZ6yXV0npJm1GkoSDJS3OhneKKR0anm2Ee2w0B2dvYBr30lhOh/D580jDLn3mmobR1xtZ79h2hmvRZ7D5tQ9LcJ2QncfEwO/1xbw54mP39fXcMJIxM4pTDxsHbcCSGEEIPV2rVreeONNzj22GNjx4LBIE899RQpKdG1bZcuXcrmzZtZvnw5OTk5ADz11FMsWLCAb7/9lilTpgDRIOyPf/xjrNtt0aJF/OhHP+Luu++OddL1ZNSoUeTm5vLQQw/xyCOPYLFY+Otf/0pNTQ01NTUAHHXUUVgsFh544AF+/etfo6oqDzzwAJFIhOrq6r58asQQImGbEKLfRVSV3Y3+6AYG9V621Pq67VbrytY6H8eP7Pq2247NIcms67SzZE87LAohBpbNoGVMmpkx+0w19QTDlLsCsfCtpMnPrkY/Dd69IdyFE1IG/N93klnHz2dl8vaWBpbtaebjnU72NPm5dHLagAaBQgghjkxtnWdm896fq9dccw1XXXUVWm3Hn0vr168H6DBd9Mc//jGXXHJJp6miX3/9daexB+vjjz+msLCQcDhMMBjk5JNPZtGiRbHbc3JyYkFb22PKzs6OBW0ARUVFJCQksH379ljYlpOTQ3Z2dqxL7qijjiISibBz585ehW16vZ5nn32WW265hfHjx6PVajnuuONYuHBhbExKSgrPPPMMv/71r3nuuefQaDScddZZTJw4sdPzK0QbCduEEP1OVeFXH+7BHz7wDjOdBvzh7ndAzLLLbqJCDBUWvZbCFDOFKXtfLERU2NSs5ZmlOzBoFaZlW7u8r6qqfFnSzKxc22HZuECnUTh3XAoFSSZe+a6O7fU+Hv+ygh9NTaNANmARQghxGHW1o6fBYOgw7bGnsXq9vssNCfpyp9Cjjz6ahx56CL1eT0ZGRqfr7XstVVW7fHOtu+Nt2m47kDfmJk2axEcffYTL5SIYDJKSksIZZ5zBpEmTYmPmzZvHV199RUNDA1qtloSEBKZMmUJ+fn6vryOOLBK2CSEOWUsgzJqKFvISjWRldb5dq1EoTDGxoca733OlWnSxqaBj0swUJBkxyI5/QhyxtBqFk8ZkMN4Rpskb6vaX52+rPDy6rIIUi47zxqVw4qiEw/J/x9QsK1k2Pc+vraWmJcifv67izDHJHDfMPuAdeEIIIUS8sFgsFBQU9Hp8UVER5eXllJeXx7rbtm3bhsvlorCwMDauvLycqqqqWFfc6tWr0Wg0jBgx4oBrdDgcQHTThHXr1nHbbbd1GpOcnAzAsmXLqKur48QTTzzg64gjg4RtQoiDUu0OsLLMzapyNxuqPYRV+N7oJI4e1/X4olRzp7DNoFUYlWyKhWtFqSbZ2U8I0SWNopBk7vrXFlVVefm7OgDqPSH++k01r26o45xxKZxSmIhR17+hW6bdwE1HZ/HKhnrWVrbw5uYGdjf6uHBiar9fWwghhBiKjjvuOMaOHcv111/PfffdRygU4o477mDOnDlMnjw5Ns5oNHLddddx11134Xa7+c1vfsP3vve92BTS999/n4ceeoilS5d2e6133nmHlJQUcnJy2LJlC3fffTennHIK8+bNi415+eWXGTVqFCkpKaxevZq7776bq666ilGjRvXfkyAGNQnbhBC9ElFVttf7WFXmZmW5mz1NnXcT/LrM3e1mBGNSzWTa9B02MRieZETX3XaiQgjRS99Ve9hc2zHMb/SFeW5NDa9vrOfsscmcUpSIRd9/66oYdRounZzK8EQjb21p4NsqDxXNFfx4ajqZMt1dCCGEOCCKovDcc89x1113ce6556LRaJg/f36Hdd4Ahg8fzumnn86PfvQjmpqaWLhwIQ8++GDsdpfLxc6dO3u8Vk1NDffddx91dXWkp6dz/vnnc+ONN3YYs3PnTh566CGamprIzc3lF7/4BT/96U/77PGKoUfCNiFEt/yhCOuqWmIdbE2+cI/ja1qC7KxroasVlWbm2piVZ++fQoUQRzSzXsOE9M7dswBOf5h/flvLfzbVc+bYZE4vSsJq6J/QTVEUjhvuIC/BwOJva6lpCfHk8kq+PyGFadm2frmmEEIIEe+efPLJHm+/5ZZbuOWWWzodz8nJ4R//+Md+z/+Tn/yESy+9tMvbLrzwQi688MIe73/FFVdwxRVX9Djmjjvu4I477thvLUK0kbBNCNGB0xfi6zI3K8vcrKtqIdDLTQ10GoXJmRaC3WxmIGsXCSH6S2GKmQdOHMbGag8vb6hjXZWn05jmQIQX19Xx5uYGzhidxPdGJ/fbzqHDk0zcdHQ2L66rZVu9j3+tq2N3o58zxyZLN68QQgghxBFAwjYhRAcbqj38+euqXo11GLVMz7EyM8fOlCwrFoOWrEwHlZUt/VylEEJ0Nj7Dwv0Z+Wyp9fLKhjpWV3T+v6glEOHl7+p5e3Mjp49O4qwxSThMff/rkN2o5aczMliyvYmPdzpZVtJMqcvPZVPSSexm7TkhhBBCCDE0yG97QhyBQpFot1pXHRZTs63oNBDqukGNXIeBmbk2ZubYKEo1o5UuDSFEnBmTZubuBXlsr/fyyoZ6Vpa5O43xhiK8trGepbtdPHPWCDT90H2rURROK0pieKKR/1tfx56mAI99WcGlU9IYnWru8+sJIYQQR5Lupp8KEQ8kbBPiCOH2h1lT2cLKsmbWVLRww5ysLtdQs+i1TMiw8m1rd5pGgXFpZmbk2piRYyfHIQt9CyEGh8IUM3fOy6W40ccrG+r5qqS505iTCxP7JWhrb1y6hZuOzuKfa2spcwX466pqTi5M5ISRCf1+bSGEEEIIcfhJ2CbEEFbZHGBVeXT9tY01HiLtll9bWe7udsOC44bZseo1zMy1MS3bhqOf1jUSQojDoSDJxO3H5VDS5OfVDfV8sceFCtgNGk4rSjwsNaRY9Fw/O4s3N9ezvNTNku1N7G7yc8mk1H7bsEEIIcTQImsgCzF4SNgmxBASjqhsr/exsqyZleVuSp2BbseuKnMTjqhdTgM9YWQiJ4xM7MdKhRDi8MtPNHLLsdlcOCmF1zbUk+swYtF3HXTVtgR5dUM9541PJsPWNx29eq3CBRNSGZZo5LWNDWyp9fLEVxX8aGo6+QnGPrmGEEKIoUtRFCKRCBqNZqBLEWJIi0S6WVPpAEjYJsQg5w1G+Gx7LUu+q+CbcjdOX7hX93P6w+xp8jMi2dTPFQohRHzJdRi58ejsHse8vrGeD3Y08dHOJuYXJHDB+BRy+igQm5lrJ8dh5J9ra6jzhPjTikrOGZvC7DybdC0IIYToVkZGBrt378Zut0vgJkQ/iUQiNDc3Y7VaD+k8ErYJMYh9ssvJH1dUdpge2hODVmFShoWZuXam51hJsej7t0AhhBiE6j1BPtrpBCCiRv+v/azYyXHDHPx8oYO+eIsix2HgxqOzePm7er6r9vDqxnp2Nfq4YEIKBq28gBJCCNGZ2WzGarXidnfe+OdwMxgMBALdz6I5nOKllnipA6SWQ2W1WtHpDi0uk7BNiDgWjqjsafIzPMnY5SLaWXb9foO2BKOWGa27h07OsmLSyYs4IYToyZubG2K7NreJqPD5bhdLn/uakwsT+eHkNGyHuJ6lRa/lx1PT+LTYxXvbGlld0UK5K8BPpqWTZpU3Q4QQQnSm1+txOBwDWoOiKGRlZVFZWYmq9vJd/yFeS7zUIbXEDwnbhIgjLYEwW+u8bK71sqXOy7Y6H75QhD+eUdDlej4jk03oNEqnF4X5CQZm5NiYmWunMMXU5bpsQgghunbOuBQU4P3tTQTCHf9/VYEl25tYXtrM5dPSmTfccUhTPxVFYeGIBIYlGlm8toYqd5DHv6zgB5NSmZR5aNMXhBBCCCHEwJCwTYgBoqoqNS1BNtd6Yx8lTX66yvu31Hq7DNsMWg2jUkxsr/cxLs3MzFwbM3JsZNn7ZjFvIYQ4EiWbdVx+VAbnjk/hrc0NvLetEV+o4//OTl+YJ76q5OOdTn42I4PcQ1zPbWSyiVuOyWbxt7XsavTz/Npa5hf4Ob0oSd4wEUIIIYQYZCRsE+IwCYZVdjX62NIarG2p9dDYy80MNtd6OGlUYpe33XxMNmOG5dJUX3PEteYKIUR/SjTpuGxqOueMS+GdLQ28tbkB/z6dbt9Ve7jhvWLOHZfC+eNTMB7CVH2HScc1MzN5b1sjnxa7+KzYRUmTnx9NScNhkl/ZhBBCCCEGC/nNTYjDIKKqXPHGDpz+3oVr+2rwhLq9LdNmwGzQ0nSQtQkhhOiZw6jlkslpnDQqiRc2NPH5jroOt4ci8MqGepbudnH97CwmZFgO+lpajcL3xiQzLNHIS+vr2NXo57EvK/jR1HRGyu7RQgghhBCDgoRtQvQBVVWpaA4SUVXyuphKpFEUhiUZWV/l2e+5dBqFkckmxqaZGZtmZkyqmUSz/FMVQoiBlm7T8/tzJvHGym38dVUVdfu8EVLljv4c6AuTMq1k2Q08v7aGyuYgT6+s4rSiJBYUHNoacUIIIYQQov/JK3ghDkI4orKt3stHJXtYuauaLbVenP4wxw2zc+uxOV3eZ0yqucuwzWHUMibNzNjUaLg2MsWEQSs7hgohRLyanWdnUoaFl7+r460tDbFdoecPd/TppgZpVj03zMnitY31fFPewn+3NrKnyc9FE1OxGA5tJ1QhhBBCCNF/JGwT4gCEIiqf7HLyynd11HYxtXNzrbfb+45NMwOQ6zBEO9bSzIxNs5Bt10uXghBCDDJmvYYfT0tnfoGD/7eqmhKnn59MS+/z6xi0Gn4wMZWCRBNvbK7nu2oPlc0V/HhaOllZfX45IYQQQgjRByRsE6IXwhGVL/a4eGl9HVXuYLfj6jwhaluCpFn1nW6bmGHhhfMLcRilG0EIIYaK4UkmHjwxn4rmQLdT/iOqyttbGjhxZCLWg+hIUxSFOfl2chIMLF5bQ50nxB++quSEJgWH4ifbrifTZkCvlTduhBBCCCHigYRtQvQgoqosL23m/9bVUeYK7Hd8kllHXTdhm16rQS85mxBCDDkaRSHX0Xm9zjb/2+nkH2tqeXNTA5cflcFxw+wH1dGcn2DkpqOzeXF9HVtrvfxvaw3BYBAV0CiQbtWT4zCQ4zCQbTeQ7TBgk+mmQgghhBCHnYRtQnRBVVVWV7Tw4rpadjX6ux03ItXK6GQ9Y1rXW0u3ypRQsZca8EOzEzwthI161D5aOF0IMXi4fCH+ubYGgEZfmMe+rOB/Oy38bGYmWXbDAZ/PatBy5VHpbKjx0hgxsrmslnJngJZghCp3kCp3kNUVLbHxiSZtLHhrC+FSLDo08rNKCCGEEKLfSNgmxD48wTD3flLK1jpft2OKUkxcOiWdU6aOpKqqSkKUI4QaiYC3BVxN4HKiupqguSkaqLmaUF3O6Peu1mO+vWv4VQCYrZCZg5KZu/dzVi6kZaLoOndDCiEGvxfW1dIciHQ49m2Vh+v/W8z5E1I4b1wy+gPcFEejKEzOtJKVlUVlto5IJILTH6bCFaCiOUC5K/pR5wnR5AvT5POyqd2aokatEg3fWkO4bIeBLJv+gOsQQgghhBBdk7BNiH1Y9FqM3bzgKEgycsmkNKbnWNFoNNLFNgSowWA0GGtuigZo7cOyDgGaE9xOCIcP/mLeFijehlq8LXrttuMaDaRltQZwOZCZuzeQszkO6fEJIQbWeeNSqPeEOnSbAQQjKi+tr+PzYhc/m5nB5EPYxVRRFBJNOhJNOsalW2LH/aFILHyrcAUobw5Q2RzEH1YpbvRT3K5zu20aavsuuBy7AZusMyqEEEIIccAkbBOiCxdPTmX9hyWx73MdBi6elMqcfLtMvYlTaiQCwQAEAhDwQ8CPv6GaSPGOaAdaa4Cmupx7w7TmJvC09HziwyESgepyqC5HXRc9FAvibI5o+JbVGr5l5EJWDqRkoGjlRbAQ8S7TbuA383NZUerm2W+qqfd23Mm6ojnA3f8rZe5wB5dPSyepm00WDoZRp6EgyURBkil2LBxRqW0JRgO4ti645gAtgb3TUNdU7v1/McGo7TAFNcch01CFEEIIIfZHwjZxxCpz+cmxG7rsThubZuGobCvlrgAXTUxl7nAHWo28sDgYqqpCKBQLwLr6UPc95u/FmHahGgF/NGjbR80APN4+53bBjk2oOzYB7UI4nQ7Ss1u74PbphjNbuj2dEOLwa9tNdHKWhZfW1/HfrY1E9ll9YOluF6vL3fxwShonjUrst585Wo1Cpt1Apt3AUa3HVFXF1ToNtbytE645QG1LCKc/jLPWy+Z9pqFmteuAm6p3YFRV5KekEEIIIUSUhG3iiFPuCvDv9XV8scfFr+fmMCvP3uW4X8zJwmbQopOQrUeq2wW7tqLu3ALF26hsaSbkcbcGZq2BmBrZ/4kGG7MV7AngSABHIoo9AeyJ0a8dCa23JYI9EcViIV2noXr9GtTKUqgqR60qh6oycDYe3PVDIagogYqSWAAXe+2ekBwN3bJyO4RwJKcd8sMWQhw8i17LFUdlsKAggadXVrGtvuPaoC3BCP9vVTX/2+Xk2pmZjEg2dXOmvqUoCgkmHQkmHWP3mYZa2a77rdy1dxrq7iY/u5v8KMBbW10YlAjj081MzLBQmGJGr5WfnaKjSCTCjh07aGhoIDExUZbiEEIIMaRJ2CaOGNXuAK9sqOeTXc5YR8GL6+uYkWvrcjpMokn+eexLjYShojQarO3cgrpra3T6Yzuhbu4b9zSaaFjWGqApjtavuwzQElD0vd9FUFEUdBlZaCYoqOOndbhN9bREp49WlkFVGWpVGVSVQ00lhA/y2XQ2gLMBdet30Wu0HTcYqMrOJ6TVgU4Pej3oDCh6fbRTTm9od1zf7feKXgc6Q7vjXYxtHS9TXYXobESyiUdOHsaHO5pY/G0tLftsoLC93sctS3bzwAn5HdZgO9yMOg3Dk0wM72IaamwtuOYgVV4VV0uQr8vcfF3mxqhVGJtmZkKGhbFpFsx62XhBgM/nY8GCBQDs2LEDs9k8wBUJIYQQ/UfSBDHk1XuCvLqhno92NhHap8FqT5OfL/c0c9xwWYS+K2qLO9q1tmtLrHOt/Q6b8U4xW1Btjr0BWU8BmsWGojn8LwgVixUKilAKijocV8NhqKveG8BVlqFWl0NlGbQ0H9zFAgGCu3d0Onyge+ke0HhFs09Y1xrq6fXUpmUQGT8NZs5FMcnUV3Fk0SgKpxQmMTvXzj/W1PDZbleH20cmmxidGn9hRPtpqNOyo28mpGVk8OXG3ayvamFDtQenP8y3VR6+rfKgVaAw1czEdAvjMyw4ZMOFI1pycjKaAfhZK4QQQhxuEraJIcvpC/GfTQ28t62RQLjreMCi19ASPITdJYcQNRKJBjvtu9YqS/vnYooGjEYw7PthiH2tdLqt40fXtxti51UMJrJzc6msrIyuGzfIKFotZGRDRjbK5JkdblObXR264KKfy6C2Ov6m7KqRvevq7cNXWgxrVsCr/0CZPR9l3ikouQUDUKQQAyfRrOOmY7I5fmQCT6+spqI5gEaBa2ZmDpq1QnUaDUWpZgpTTJwzLpkyZ4Dvqj18V91CTUuILbVettR6eW1jPcMSjUzMsDAx00KqRT/QpYvDyGKxsGHDBrKysgbtz2YhhBCityRsE0OO2x/mzc0NvLO1AV+o61/kjFqFM0Yncfa4lCP2XXbV0wLF21B3bkHd1dq1drA7cxoMMLwQZcQYkiZOpcnnj3YvGYxdh2paXb+v1TKU14JR7A6wj0MpHNfhuBoMQm1lNIirbA3iqlvXhvN6BqjaXvB5UT97H/Wz92HkGJR5p6JMP+aApuoKMdhNyrTy1OnDeWNTA95QhJE9rNcWUVW0cfp/nEZRyE80kp9o5PTRSVS724I3D6XOQGytt3e2NpJp08eCt+42LBJCCCGEGIwkbBNDhicY5r9bGnlzcwMtwa67e/QahVOKEjl/XAqJ5iPnr7+qqtHgZVe7rrWKEjjYd5VT0lFGjoERY1BGjobcAhRdNECzZmXhknesB4Si10N2PmTnd9gVUFXV6EYM1eUkRoI01dZGg7lQAILB6EcoGN3RNdT2dRA1diy097Z9x7Z9H+6jDtGd0SnL6st/QznmeJS5p6BkZPfNuYWIc3qthu9PTO1xzOZaD3/+uoqfzcgkK+swFXYIMmwGMmwGThiZSJMvxIZqDxuqPexo8FHlDlLldvLRTifJZh0TMyxMyLBQkGTsci1VIYQQQojB4shJG8SQ9uGOJl74thaXv+sX/FoFThiZyAUTUkizDv1pK6rPA8XbW7vWtsLOLeBxH9zJdHoYPgqlLVgbMQYlMblvCxb9SlEUSExGSUrptzBUjYQhGGoX4LWGdF0Fc8EA+P0YNq3Bt+rLrqe+tjSjfvgm6odvwtjJaOadCpNnoujkx5Y4coUiKk+vrKbUGeDOj0tYUuxhuF1hWIKR/EQDaVZ9XIdUiSYdxw5zcOwwB55gmE01Xr6r9rCl1kuDN8Tnu118vtuF1aBhfLqFiRkWimRn0yHD5/Nx6623YjabWbRoEUajcaBLEkIIIfqNvGoRQ4I/FOkyaNMoML/AwYUTUsm0D80paWokQrC8hMiKL1B3bkbduRXK9xz82l3JqSgjxsDI0Sgjx0JeAYpu6AeU4tAoGi0YtdFpw70ZryikXfBDKjasI7L0A9RlH0W777qyeR2RzesgIRnluBNRjjsJJTmtD6sXYnB4Z0sDe5r2rn/4xc46vmh3u0mnYViigfwEI8MS934kxOHu2ha9luk5Nqbn2AiEI2ytiwZvm2q8tAQirCxzs7J1Z9MxaWYmys6mg14kEuGNN94A4P777x/gaoQQQoj+FX+/fQlxEE4uTOSNzQ3Ue0KxY8cOs/ODiankJgy+d07VYBCam6DZCa4mVFf0c9sx1dUELmf0dreTqoOdwqfTQf5IlJFj9k4LTUrpuwcixH4oKelozr4U9YyLYN3XRD5fApvXdT3Y2YD635dR330VJk2PdruNnzogu8gKcbipqso3FT2vq+kLRdha52Nrna/D8QSTlmEJRm47NhtHHAZvBq2GiRlWJmZYCUdUdjb4otNNazw0+cKsq/Kwrm1n0xQTEzIsTEi3xOVjEd3T6/Xcd999OBwO9Hp5E08IIcTQJr+liEEjHFEJRLru1jJoNVw4IZW/rKxiZq6NiyelUpDU/eLSh5uqqtHNB5qbWkMzZ4cATW0N1WIBmvcgNyrYn8Tk6AL0I1rDtfyR0XW+hBhgik4HRx2D9qhjUKvKUZcuQf3yf11Pf1YjsG4lkXUrITUDZe7JKMecgOJIPOx1C3G4KIrC/QvzWLK9iX+tq8XTzdqkXXH6wmwJerEaut4QqLI5wPZ6H8MSjWTbDQM6bVOrUShKNVOUauacccmUugJ8VxXdYKGmJciWOh9b6ny8vrGBYYlGJmREp5seCUtEDHZ6vZ6rrrpKdiMVQghxRJCwTcS9kiY/nxY7+azYxdRsKw/n53Y57viRCYxINlKYYj4sdamhIKG6GtSSnajOxmh41txDgBYO7e+UfUurg/wRKCNGxwI2klNltzcR95TMHJTvX4F69qWo33yJunRJdN3BrtRVo/5nMepb/4cybQ7K/FOhcLz8PRdDklajcProJI4bZmd5qZvqgJbN5dGppd1tDNQmL8GAVtP1v4tvyt38bXVN9BoK5Dqia8Dlt01FTTCSbjv868EpikJ+gpH8hL07m25o3dm0pN3Opv9t29k008pcrR0CYSw6Rf4fEEIIIcSAkbBNxKUmX4gvdrv4tNjJzoa969N8usvJngYPXa2+ptMo/Rq0qa5G2LYRddtG1O0boXwPlfH0rmxCEowYHZ0SOmIMDBuJYhh8U2iFaKMYjChHL4SjF6KWFqN+/j7qis/B7+08OBxCXfUF6qovICsPZd4pKHMWoFhsh79wIfqZw6TjlKKkWIdQJBKh3huipMnPntaPEqefUmeAQDj6cyq/hyUV2q8DF1Zhj9PPHqcf9jTHjpt0Cnnt1oLLTzAyPNF4WHf2btvZ9PjWnU03tgZvbTubVu9o4vM9LQSDQQxahSSzjiSzjuR2n9u+thk0EsYdZpFIhIqKCvx+P3q9Xp5/IYQQQ5qEbaJHkY/fRgkF8U2dgWpNBJuj364VCEdYVebm02InqytaiHSRY0VU+OuXu7huev+vK6bW16Ju3xAN2LZvhKryfr9mJ1ot2BOiH45EFHsiOKJfY09AcSSiOBLJKBxDtT94+OsT4jBR8gpQLr0W9fwfo674HPXz96Fsd9eDK0tR//0s6n/+iTJjLsr8U1GGFx7OcoU4rBRFIdWiJ9WiZ1r23oA5HFGpcgcpafKTaO56Cil0DNu64wupbK/3sb2+43pwo5JNvHRF9sEXf5ASTTqOGebgmHY7m26o8VDpgbpgEH84+tir3F3/bNRromFcskVHsin6OcmsI8mkJdmixy5hXJ/z+XzMmjULgB07dmA2H56ZCEIIIcRAkLBN9Ej99F3UmkpqX/9n9EByWnRqYv5IlPyRMGxEdIfAg/yFVFVVttR6+bTYxbI9rv1OgwEoa/LiD0Uw9OGaMqqqQk0l6rZ24Vp9TZ+dvwOTORaWYY+GZW0BmtJ6jLZjFtt+n1tFUdCmpKHI+ifiCKCYLCjzT0Wddwrs2hrtdlu1DEJdvKAOBFC//Bj1y49h2Khot9vMuSjG+FnPUYj+pNUo5DgM5Dh63o07zaqnwRuiznPgyx0k99DZVtsSxKzTYDN2H/T1hbadTWfk2snKyqKkrIJGb5AGb4gGT4hGX/RzgzdEky+E0xcmGFGpaQlS09J1GKfTKCSZtR264dp/bTdqD/u02qHAbDZLiCmEEOKIIGGb6JbqaYGayo4HG2qhoRb126+JxTr2hOiUxfyRKPkjIH9kdNHyHn6ZqmoO8FlxdJpod+86t5dk1jF/uIMFIxKYM67gkBfWVSMRqCiJhmpt4Zqz8eBOpmjA7uix+4y2Y/YEmdopRB9QFCW6FuHIMajfvwL1q09QP18CNRVd32HPDtTFf0J99TmU2QtQ5p2KkpN/eIsWIk798rgcANyBMKWt66CVOPdOSXUHun8jbFhS9z/TXvi2lqW7XQxPMjI+3cL4dDPj0y0k9PMuonqtQppV3+2mCcGwSpMvRKM3+tHQ+tH2tcsfJhRRqW0JUdvSdQCp00S769oHcO0DuYTDOL12sLBYLOzcuVM2SBBCCHFEkN8ERPdKi3s3rtkJG9agblizN4AzW1s74KLhmzJsJGRko2ii724/uqyCHQ2+bk8JYNQqzMmzM39EApMyLGg1B7/YsRoOQ+muveutbd8ELc37v+O+TGYYNRalcDyaovFkTD6KandLNHATQgwIxeZAOels1BPOhK3fEfnsffh2BXS1e7HXE+3Y/fRdKBqPMvcUNEcdc/iLFiIO2QxaxqZbGJtuiR1TVZVGXzi6DlxbENcaxgXCardrwamqyoYaDypQ3OinuDG6kQFEN2uIhm/RAC7Fcnh3Et1fGBeORMO49iFcY7uvm3xhQhGo83TfDahVYGyOkxPzzeQl9NxZKIQQQoihR8I20T2bHWX+qaglu1DKdqMG9r+mS4y3BbZ+h7r1O4BoCGcwQl4BSv4ILtVn8M9mB6XWDEKavX8NFWBihoUFIxKYnWfDoj+4qSdqMAi7t6Nu2xAN13Zs6XpR9f2x2qFwHErheJTREyC3AEUbrUlRFLQJSSgen7w7K0QcUDQaGDsZ7djJqE31qMs+Rv3iA2io6/oOrRuehF/+G40LTiWSkgFZeZCVi2KydH0fIY4wiqLEurWmZlljx8Ot0zC761KraQlS300QVeoMUOoMsGR7EwBZdn0sfJuQbiHddnjDt31pNQopFn23IWA4ouL0hzt1xrVNWW30hoiosK3azcayRmbl2TitKAmboX+n0wohhBAifkjYJrql5AxDueQaFEUhMz2NyrXfENmzE0p2opbshJJd4DuAACvgh51bUHduYRLwGBBUtJRYM6lJzsM8opCCKeNIGpWBYjywqZaq3xddv6mtc23XVggGDugcACQkoRRNgMLxKEXjo7saaqRrTYjBRklMQTnjQtRTz4cNq6PdbhvXQFfBeLMT99v/7ngsOQ2y81Cy8iA7P/o5Kw/FYu18fyGOQFqNQpbd0G3HeZkzgEGrxHZD7Ullc5DKZicf73QCkG7Vcfm0DObk2/u05r6i1ewNILsSUVUavGG+qgrx+dYqVpS6WV/l4bSiRGbn2Y/Ytd78fj933XUXFouFO++8E4NBOv6EEEIMXRK2iV5RtDqUnGFosvNhzgKgdd2zuirUPbugZCeB3TuI7NmJ0evu9Xn1apiR7nJGusuhZAV8BhFFA5k50amnbevA5Y3o8CJX9bSg7tgUDde2bYA9OyAcPvAHlpIeDdUKx0dDtvQsWbhXiCFE0Wph8ky0k2ei1lahfvEB6rKPo9Pfe9K2PuWGNQB7p8gnpUZDt+x2IVx2HorF1u2phDgSHZVj4/8uKGR7vY8NNR421njZUuvBF9p/+FbTEsJq6PqNrrZO8nj+Wa1RotNUf3ZsPhOSFF7fWEdlc5DXNjbwdambc8enMCzxyFu/NRwO83//938A/OpXvxrgaoQQQoj+JWGbOGiKRoMvOZMVLVY+deWzLnMWaoZKit/JCHc5I5rLmU0tw5xl0FTf+xOrEagsRa0shRWf7X2Rm5aJkltAlauB8K5tXXeo7E9mzt7OtcLxKClpB34OIcSgpKRlopx7GeqZF6OuXYH62fuwbcOBnaSxDhrrUDetBdqFcAnJ0dAtO781jMuPfm+Nz84cIQ4HvVbDuHQL41rXgAtFVHY2+NhY42FjtYdNtV48XexCrtPA6FRzl+esaQly25I9jEu3MCHDzIR0C/mJxrjtFhuZbOLmo7P5sqSZJdsbKXUF+MPySmbl2ji9KKnfd2qNJzqdjttvvx273Y5OJy9BhBBCDG3yk04csIiqsqHaw6fFTr4qae74LrWiUG9KpN6UyKrU8bxv1PLcOaPQtTihZFds+qlashNqqw7swrVVqLVV7H/v0r21kDMcpah1SmjhOBRH0oFdUwgx5Cg6PcqM42DGcaiVpairvsBUV4V31zaoqYoG/gfK2QDOBtTN64B2IZwjsWP4lpUf7YizO/rq4QgxaOg0CqNTzYxONXPuuBTCEZU9TX421HjYUO1hU42H5kCEwhQzRl3XnW0bqj04/WGWlzazvDS60ZHNEA31JqRbmK9Ycagq8RS9aTUKc4c7mJpl5b9bG1hV3sLXZW7WV3s4rTCROflHxtRSg8HADTfcILuRCiGEOCLEXdi2adMm3n77bYqLi2lsbOTWW29l5syZAIRCIf7973+zdu1aampqsFgsTJw4kYsvvpjk5OTYOYLBIC+88AJffvklgUCACRMmcOWVV5KSkjJQD2tIKHX6+XSXk8+Knd3uvtVeulXH/IIEgpEIekciTJiGMmFa7HbV44bSYtSS6DRUdc9OqCo/uBe6ABoNDBsV7VgrmhDdNdQqU7uEEN1TsvLQnHUJqa0v/iIBP1SXo5aX7O2wrSiBmsqudzfdH1cTuJo6bhYDYE/ocjoq8oaAOIJoNQojkk2MSDZx5phkIqpKqTOAL9T9v7WNNZ3XinUHIqwsc7OyzM1za2pIMGqZmm1leraNqdnWuNmYwG7U8oNJaczOs/OfjQ2UNwd4fVMDK8rcnDcumeFJpoEuUQghhBB9JO7CNr/fz/Dhw1mwYAGPPfZYh9sCgQDFxcWcd955DB8+HLfbzT//+U9+97vf8fDDD8fGPf/886xevZobbrgBu93O4sWLefjhh3nkkUfQyGL3B2xFaTNvflzGpqrm/Y416zQcM8zOwoIExqabe3ynVrHYYPRElNETY8dUvx/K2gVwJbugfA+Euwj3dHoYUdQaro2HEWNQTF1POxFCiN5Q9IborsO5BR2Oq8Eg1FSgVkTDN7WyBCpKoabi4NaLbHZCszO65iTtQjibnephIwknpUFmLkpmLmTlQEpGbCdkIYYqjaLsdy2zjTWe/Z7H6Q/zWbGLz4pdaBQYk2pmeo6N6Tm2uFgrrSDJxI1HZ7G8tJn3tzVS7grw1IoqZubYOH10EvYhOrVUVVUaGhrQ6XTS1SaEEGLIi7uwberUqUydOrXL2ywWC7/5zW86HPvJT37CHXfcQV1dHampqXg8Hj755BOuv/56Jk2aBMD111/PNddcw/r165kyZUp/P4Qhx+UP9xi0aRSYmmVlfkECs3Jt3U796A3FaISRY1BGjokdU0PB6Ivbkl1QVY4jIxN3Zh7q8MLoC2MhhOhnil4POcNQcoZ1OK6GQtHArbI0GsRVlqJWlES7dLt6k2B/3M0ENn679/xtX+h0kJYFWa0BXFsQl5mDYrYc9OMSYrC5//g8NlR72FDjZWONh2p3z4tLRFTYVOtlU62Xz4qd/PGMEYep0p5pNQrHDnMwOdPKu9sao5155W6+q27hlMIkjs63o9UMramlXq+XiROjb7Du2LEDs1neIBVCCDF0xV3YdqA8Hg+KomCxRF9s7Nq1i3A4HAvaAJKTk8nPz2fbtm3dhm3BYJBgcO8vbIqixH4JiOcdrw6HY4c5ePabavz7TOsoSDKyoCCBucMdJFv0/XZ9RW+AYaOiU0QVBUdmJp6qqgF/V7Tt78VA//2Ilzra1yC1dBQvtcRLHe1rGOy1tIVw7BvChcNQW7l3OmpFSXRKamUZhHq98uReoRBUtoZ5bddouy0hGSWrNXhrC+KyciEpFeUgu7mHyp9PX5NaBr6OTLuRTLuRE0ZFp1zXtgTZUO1hY+u6bxXNgW7ve1SOrds6vcEIZn3fzX7o7fPiMOn4waQ05uTZeX1jPeWuAG9ubmBluZvzxqdQ0AdTS+Pt70rb1wNZT7w8J+1rkFo6ipda4qWO9jVILR3FSy3xUkf7GqSWgaWoA51Y9OD73/9+hzXb9hUIBLj77rvJzs7mF7/4BQDLli3jL3/5S2xr8TaLFi0iPT2dn/70p12e65VXXuG1116LfV9QUMAjjzzSR49k8LvznQ18uKWGFKuBU8ZmcNr4TIrSZZc9IYQ4EGo4RKiqglDJLoKlxQT37CJYuotQ6W7UgL9Pr6UYjehyhqHPHY4ut+3zcHQ5w9CYZG0oMTSVNXn5alc9y3bVsbqkiUB47xuF/++iqRyV1/W6iBc/v5JQROXYESkcMzKFydkJ6LSHd+mRiKry6bZaXvu2jBZ/dHr6MSNSuPCoPBLN/femphBCCCH63qDtbAuFQjz55JOoqsqVV1653/H7yxTPOecczjjjjNj3bclrbW0todBBTAUaQhRF4Uczh3F0tonJmZbotIawm8pK94DUkpmZSVWcdLbFQy3xUofUEv+1xEsdR3wtGj0MHx39OK71UCQM9bVQUYKtxYVr++ZoJ1xVGbj3v15mV1S/n+CubQR3bet8Y0o6SmZOu3XhWj8nJMU6To7YPx+pZVDW0VZLbmYmc7N1HJeVgX9WGuurWlhV7mZzrZd0jZfKSl+n+9W1BNleG/2dpri+hRdWlWDVa5iSZWV6jo2jcmwkmg7sV+aDfV7GOeDm2em8u7WRr0ub+WxrFct31nBKYSLHDnMc1NTSePszioda4qUOqSX+a4mXOqSW+K8lXuqQWvqfTqcjLS1t/+MOQy19LhQK8cQTT1BbW8vdd98dm0IKkJiYSCgUwu12Y7Pt3YnS5XIxevTobs+p1+vR67t+13Co/KU4FKMz7TgiblRVjYvnI17qgPipJV7qAKmlO/FSS7zUAVJLjKKB1AyUtEwcWVm0VFbGalGbXVBdhlpZBlXlqFXRz9RWHfzuzfU1qPU1sHEtHR6xyRybhuoqHEskaxjqiCIUzcAv2C5/V7oWL7XESx2wtxaDVoltjND+tn2truj85mFLMMKXJc18WdKMAoxKMTE928ZROVZGJpt63ACqq1oOhFWv4fsTUpida+P1TfWUOqNTS78ua+bccSmMTD64ztR4/DMaaPFSB0gt3YmXWuKlDpBauhMvtcRLHSC1DLRBF7a1BW1VVVXcc8892O0dpzKOGDECrVbL+vXrOfroowFobGykpKSESy65ZCBKFkIIIQ6aYneAfRzKqHEdjqvBINRWQlVrEFddjlpVHu2G8+5/x8Yu+bywezvq7u04l38aPWZPQJk8E2XKbBg7CcUw8Ls5CtGXNlT3/O9FBbbX+9he7+Ol7+pIMmmZlm1jeo6VKVlWLPr+CaPzE43cMCeLr0vdvLutkcrmIH/+uoqjsq18b3QSjgPsthtofr+fBx98EKvVyo033ojBIJtcCSGEGLri7qe0z+ejqqoq9n1NTQ27d+/GZrORlJTE448/TnFxMbfffjuRSISmpiYAbDYbOp0Oi8XCwoULeeGFF7Db7dhsNl544QXy8/M7bJoghBBCDGaKXg/Z+ZCdT/seG1VVwdkYDeFaw7dYN1x9zYFfqNmJuuwj1GUfgcEIE6ahTJ6FMmk6is3RZ49HiIFyw5wsTh+dxDflbr4pd7Orsef1Ext9Yf63y8n/djnRaRT+ed4obIb+Cdw0isKcfDuTMi28t62RFaVuVle0sKHaw8mFiRx3kFNLB0I4HOZvf/sbANdff/0AVyOEEEL0r7gL23bu3Ml9990X+37x4sUAzJs3jwsuuIBvvvkGgF/+8pcd7nfPPfcwfvx4AC677DK0Wi1PPPEEgUCACRMmcPvtt6M5yF3ZhBBCiMFCURRITIbEZJQxHd9kUv1+qKmIhm+VZXuDuOpyCHS/i2NMwA9rlqOuWY6q0UDheJQps6IfqRn99IiE6F9ajcLoVDOjU81cMjmNek+Q1RUtrK5w822lB1+o++naBUnGfgva2rMatFwwIZVZeXb+s7GeEmeAt7c0srLMzbnjUhiVEv+bnuh0On7xi1/E3iAXQgghhrK4+0k3fvx4XnnllW5v7+m2NgaDgcsvv5zLL7+8L0sTQgghBjXFaIS8ApS8gg7H1UgEGuvbdcGVoSveRnDPzu5PFonA1u9Qt36H+vLfILcAZWo0eCNvxBG5xbsYGlIsek4alchJoxIJhiNsrPHyTYWb1eVuKpqDHcYelW3t9jxPf7GTXdWNLChIYGKGpU860PITjPxiTharytz8d1sjVe4gf1lZxdQsK98bk3TAGzkcTgaDgV/96ldkZWVR2W5dSiGEEGIoit+fyEIIIYQ4LBSNBlLSICUNZfzU6M5RWVlUrFtDZO0K1G9XwI4tPW/IUFaMWlaM+s6/ITkNZepslMkzo91v0sUiBim9Nror6ZQsK1celUGFK8Dqiuh00w013g6bL+zr4601lDR6+azYRbJZx7zhDuYXOBiedGhdaBpFYVaenYmZFt7f1sRXJc2srWxhU42Hk0YlMnf44JlaKoQQQgxV8tuvEEIIIbqkpGehOelsOOls1GYn6vpVqGtXwKZvIdjDtNOGWtT/vYP6v3fAYouu7zZlNoyfimIyH67yhehz2Q4D2Y5kvjcmGW8wglHXdahV4QpQ0uiNfd/gDfHG5gbe2NxAQZKRBQUJzB3uIMl88L+KW/RazhufwqxcG69vamBPk593tjaystzNueOSKUyJr39rqqri9XppaWmRrjYhhBBDnoRtQgghhNgvxZ6AcswJcMwJqH4fbPoW9duvUdevBHdz93f0uFFXfIa64jPQ6WHs5NautxkojqTDVr8Qfc2s734t4FXl7m5vK270U9xYw/Nra5iaZWV+QQKzcm0YdQe3tnBugpHrZ2eyqtzNu1sbqXYHeXplNVMyLZw5Npkks/6gztvXvF4vhYWFAOzYsQOzOb7CQCGEEKIvSdgmhBBCiAOiGE0wdTbK1Nmo4TDs2BwN3r5dAXXV3d8xFITvvkH97htURYERo1uDt1komTmH7wEI0c/mDnfgcDh4e10pOxt8XY6JqLRuxNCCWafhmGF25hc4GJ9uQXOAax5qFIVZuXYmZlhYsq2Jr0qb+bbKw+ZaLyeOSuSidNnARAghhDicJGwTQgghxEFTtFoYPQFl9ATU718O5XtQv12B+u1K2LOj+zuqKuzcgrpzC+prz0NWHsqUmdHppsMLo+vICTFIJZl1XDw9iwU5OvY0+vi02MnnxS7qvaEux3tDET7e6eTjnU7SrTqePK0A60HscmrRazl3fEp019JN9RQ3+nl3ayMrKtYxIdXAzBwrmXbDoT68g2I2m9mxYweZmZk4nc4BqUEIIYQ4XCRsE0IIIUSfUBQFcoej5A6HMy5Cra9FXfc16rdfw7YNEA53f+fKUtTKUtT3X4eEZJTJM1GmzoIxkw9b/UL0h/xEI5dNTefSyWl8V+3hs2Iny0ub8YW6Xrcsyaw7qKCtvRyHgetmZbK6ooX/bm3E5QvxebGXz4qd5CcYmJlrY0qWFYv+0K5zIBRFwWKxYLVacblcsm6bEEKIIU3CNiGEEEL0CyUlDWXhGbDwDFSPG/W71bB2BeqGNeD3dn9HZwPq0iWoS5eAyUzdjGOJTD8Wxk6JBnpCDEJajRLb2fTqYIQVpc18VuxkXZWH9rHT/IKEbs9R5vSTYdOj1+6/81NRFKbn2JiWbaMmYub9dXvYWOOhxBmgxNnAm5sbmZRhYWaujVEppgOeuiqEEEKI7knYJoQQQoh+p1hsKLPmwax5qMEgbFkfnW66biU4G7u/o8+L94uP4IuPIGcYyolnocych6KPj0XfhTgYZr2GBSMSWDAigXpPkM93u/hsl4vyZj/HDnN0eR9VVVn0eRnN/jDH5DtYMMLBmFTzfgNorUZhWk4SWTofLl+I1RVuVpa5qXIHWVPZwprKFpLNWqbn2JiRYyPF0j//tgKBAE888QQ2m42f/vSn6OXfsBBCiCFMwjYhhBBCHFaKXg8Tj0KZeBTqJddA8bbodNO1X0NVWfd3LN+D+vxTqP9ZjLLgdJR5p6LYuw4mhBgsUix6zh2Xwjljk6lpCeIwdj21c2udj8rmIAAf7Gjigx1NZNr0LChIYH6Bo1drsdmNWuYXJDBvuINSV4CVZW7WVrbQ4A3z4Q4nH+5wMirZxMxcG5MyLRh60UHXW6FQiKeeegqAyy+/XMI2IYQQQ5qEbUIIIYQYMIpGAyPHoIwcA+dehlpV1rqz6dewa2t0I4V9uZpQ33oR9b1XUeYsRDnxTJTM3MNfvBB9SFEUMmzdB2afFnfeVKDKHeSl7+p46bs6xqaZWVCQwDH5dmzdBHbtr5WfYCQ/wchZY5LZUOPh69Jmttf72NEQ/Xh9o8LUbCszc2wMSzQe8hRurVbLlVdeidVqRas9fGvFCSGEEANBwjYhhBBCxA0lMxfllFw45TxUZyOsXY7m8/cJle3pPDgY2Lu226QZaE48C0ZPlHXdxJDU5Ot6J9M2m2u9bK718uw31czItbGgwMG0bBt6bc//HvRahalZVqZmWWn0hlhV7mZVuZt6T4gVpW5WlLpJt+qZmWtjerYVh+ngXj4YjUbuv/9+srKyqKyslA0ShBBCDGkStgkhhBAiLikJSSgLTifzosup+Oi/RD54A7Z+1/Xg9auIrF8F+SOi67pNPxZFJ9PUxNDx67m5VLsDfL7bxae7XFQ0B7ocF4yofFXSzFclzTiMWuYOd/D9mVYSe3GNJLOOk0YlcsLIBIob/Xxd1sz6Kg81LUH+u7WR97Y1MibVzMxcG+PSLeg0EmwLIYQQXZGwTQghhBBxTdFo0EyagTJxOmrJTtSP3kZdtRTC4c6DS3ah/v0J1NcXoyw8A2XuyShW2+EvWoh+kGEz8P0JqVwwPoXt9T4+LXbyxZ5mmv1d/FsAXP4w/93aSLFrGw8dn9Pr62gUhZHJJkYmmzhvXIRvK1tYWe6muNHPplovm2q9WA0ajsq2MiPHTo5j/+vFCSGEEEcSCduEEEIIMWgo+SNRrrgJ9dwfoX76X9TPl4CnpfPApnrU//wT9d2XUY45AeX476GkZx3+goXoB4qiUJRqpijVzOXTMlhT4ebTYheryt2EIp2nZ547Ofugr2XUaZiVZ2dWnp0adzA2zdTlD7N0dzNLdzeT6zAwM9fGtGwrFn3X67F5PB4KCwsB2LFjB2az+aBrEkIIIeKdhG1CCCGEGHSUpBSUcy9DPe37qF/9D/Xjt6G2qvNAvw/1k/+ifvouTJmF5qSzYeRYWddNDBl6rRILw9z+MMtKotNMt9R5AbDqNZw4OoOm+ppO91VVlS9LmpmRY8Oo2//Oo+k2PaePTuKUwkS21XlZWe5mQ7WHMleAsk0NvL2lgQnpFmbk2hidakYj/86EEEIcoSRsE0IIIcSgpZjMKAvPQJ1/Kny7kshHb8GOTZ0HqiqsXUFk7QooKEI58WyUaXNQZFdEMYTYjFpOKUzilMIkKpsDfFbsRKfRYDZoaepi/KZaL48uq8Cq1zB/RAKnjEokP9G43+toNQpj0y2MTbfgDoRZU9HCqjI35c0Bvq3y8G2Vh0STluk5Nmbk2Eiz6jGbzXz33XdkZGQQDAb7/LELIYQQ8UTCNiGEEEIMeopGC9PmoJ02B7V4G+pHb6Gu/hIikc6Di7eh/vV3qMlp0emlx52EYrYc/qKF6EdZdgM/mJTWYxfnku1NALQEI7y7tZF3tzYyLs3MyYWJHJ1vx6Ddf7ebzRDdhGHucAdlLj8ry9ysqWihyRfm451OPt7ppCDJyMxcG1OzkkhLS5PdSIUQQgx5ErYJIYQQYkhRCopQfnobav1l0SmkSz8An7fzwIZa1FefQ33npWjgdvz3UFLSD3/BQgwAly/EVyXNnY63bYDwt9U1HD8igZNGJfZ6A4Rch5HccUa+NzqZjTUeVpa72VbnpbjRT3Gjnzc3NXDS+DDTUxXsRukqFUIIMXRJ2CaEEEKIIUlJSUe54HLUMy5CXfZRdF23htrOA33eaCfc/95BOeoYlBPPQikoOvwFC3EYuQMRJmVYWFvZQlc9Zs3+MG9ubuDNzQ1MyrBwcmEis3Lt6LX7X4dNr1WYkmVlSpaVJl+Ib8rdrNjTyLLX/8n6/2oYe/IPOHZEEgsLEnCY5OWIEEKIoUd+ugkhhBBiSFPMFpQTz0JdeAbqmuWoH70Jxds6D4xEUFd9gbrqCxg1Ds2JZ8GUmdEpqkIMMdkOA/cszKPaHeDDHU4+2tmE0xfucuz6ag/rqz0kmLSc0NrtlmnvXbdboknHCSMTmZOp5+kf/hWAUQvPZ+nuZr4qcTM7z8bCEQkkSugmhBBiCJGfakIIIYQ4IihaLcqMY1GnHwM7N0c3U1i7Irp5wr52bCKyYxOkZaKccCbK0cfLum5iSMqwGfjhlDQumpjKyrJmluxoYn2Vp8uxTl+Y1zc18J9NDdy9IJdp2bZeX0en03HxxRdjNpv5/qxsPtntYXeTn2V7mllR6mZ2ro2FIyV0E0IIMTTITzMhhBBCHFEURYFR49COGodaU4n6v3dQv/wY/L7Og2urUF/6K+pbL6LOPYXQD644/AULcRjotQrHDHNwzDAHFa4AH+xo4n+7nDT7O3e7WQwaxqcfWPhsNBr5/e9/T1ZWFpWVlYzPdPD/2bvz+Kiq+//j73NnSSZ7AoGEPRBAEFnEBYWKG7ihFrVo6/dbt1rr0lar1lpbLdXWYm2r3X7V6rduVVER963ugjsqSFEg7FsgIfs6mbnn98eEkJCEdZKZJK/n45FHJveeufc9Axjzyeecs3J7nV4tKNOa0notWF+pDzdWatKAVIpuAIAuj+9iAACgxzJ9cmW+/X3ZM74j++6rsm8+L5WVtB5YUy37yjxtef1ZmeNOk5lxnkxScucHBjpBvzS/Ljq0j84f11sfrK/UKyvLtKxo5yYjx+elK8Hb9k6llfVhJfkceZzdr+1mjNGI3gEN75Woldvr9FpBmVY3K7odOSBVJwxNV0aAH1cAAF0P370AAECPZ5JTZE45W3baGbKfLpB97Rlpw5rWA0OhyGYKH74tM/N/ZSafKOO0XXQAujq/x9HUvHRNzUvX+vJ6vbqyTG+tKddJwzPafc49nxTqq6JaTc/P0InD0tUrybfbezQvuhWURIpuq0rqtXB9pT7aWKkjGotumRTdAABdCN+1AAAAGhmvT2bScbJHHiutWCr3tWekJZ+0HlhZLvvQX2XfeUXOeZfK5I/q7KhApxqUnqBLD+urCydky+dpu8BcVhfSBxsqFXKlR5cU6/Evi3XEgBSdlJ+hEelG48eNkzFGS5YsUSAQaPFcY4yG9wpoeK+ACrbX6dWCUq0qqdf76yv10YZKHTEgRScMy1AWRTcAQBfAdysAAIBdGGOkkYfIM/IQ2cKNsv95Tvb916VQqOXAdQVy59wgc+RUmbMvlMnsFZvAQCdpr9AmSW+uKlfI3fm1a6UPN1Tpww1V6u0Lqba2tt3nNpffK1H5vXK1qqROr64sU0FJnT7YUKWPN1ZRdAMAdAl8lwIAANgNkzNA5n+vkE6aKd9z/1bdR++2GmM/ekf2i49kTv2WzLQzZXz+GCQFYqugpI1NRhoV1Ts65Gf/lscx+tNHxZo+PFMTcpN3u7bbsKxEXXFkjlY1Ti9duT1SdPtoR9Ft6J6nqQIAEAsU2wAAAPaC6dtP2Tf/UZv+86Lcx/8pFW5sOaC+Tnb+w7IL/iNn1sXSuCMjHXJAD/HTb/TXyu21enVlmd5dW6H6sG06ZxxHCVk5kqSPNlXro03V6hXw6oRh6TpxWLr6prRfoB6WlajLj8jR6sai24rtdfpwR6db/xSdsBdrwwEA0JlY0RcAAGAfOGMOlXPLn2XOvUQKJLUeUFQo92+/lXvXLbJbNnR+QCCGhvcK6KpJufrXWfm67PC+GpyR0O7Y7bUhPbF0u77/7Gr9YcHmPV57aFaifnBEjq46MkcjeiVGpqlurNLt727S418Wa3tNQzRfCgAA+43ONgAAgH1kvF6ZE8+UPWKq7DOPyC74j2Rty0HLvpA7+0cyx50mc/p5MkkpsQkLxECy36NTR2TqlOEZWl5cp5e+LtK8xx5W2JWyjz5TjqfljyG9kvb+x5IdRbe1pXV6taBMy4vr9PHGKn26qUqH9U/RicPS1ZtONwBADNHZBgAAsJ9MWoac714l56Y/SMMOaj0gHJZ9/Tm5v7hc7ruvyrrhzg8JxJAxRgdlB/SDib219tm/a8Pzf9fQNE+rcSfmp7d7jWDYbfP4kMxEXXZ4jn40KUcH9Y50un28sUq/e3eTHl9SrKJqOt0AALFBZxsAAMABMoPz5dwwR/bjd2Wf+pdUVtJyQGW57MN/k33nFTnfvlQmf3RsggIx4jiOZs6cqUAgoNtOGarNNVb/WVWud9aUa1B6ggaktT3ddHtNg658fo0mDUzRtPwMjc4OtFoLcUhmor5/eKTT7bVV5fq6qFYfb6rSp5urdGi/ZE0blqHsZDrdAACdh2IbAABAFBhjZI6cKjvuCNmXn5J9bb4UCrUctH6V3Dk/kzliqszZF8hk9Y5NWKCTJSYm6m9/+5tyc3O1ZcsW5SVYff+wRF04IVultaF2n/fm6nLVhly9taZCb62pUL9Uv04clq7jh6YrM9DyR5khmYn6/mGJWldWr9cKyvRVUa0+3VStzzZXa0JusqbnU3QDAHQOim0AAABRZBIDMjP/V3bKNLlP/J/0xYetxtiP35H94kOZU78lM/2bMr72d2IEujO/x2l3J1LXWr2+qrzFsc2VQT30RZEeWVykwxvXZ5vYL0UeZ2e32+CMBF16WF+tbyy6LSuq1aLN1fp8y46iW6ZyO/RVAQB6OoptAAAAHcBk58hz5c9ll30u9/H7pF13Jg3WN22u4My6RBp/ZKvpcUBPtqE82O66a66VPtpYpY82Vikz4NUJQ9N14rB05abuLNwNykjQ9w7rq/XljUW3bTuLbt8obNARfb3KTaHTDQAQfWyQAAAA0IHM6Alybr5b5tzvSYHk1gOKt8r9+2/l/ulm2c3rOz8g0Alqamo0ZswYZWdnq6amZq+eMzgjQf83M18XTshW/7T2uz9La0N66r/b9YPnVuum19fr7TXlqg/t3FRhUHqCvjexr645OlcH9wnIWumDNSW6871N+uenW7WqpE52192EAQA4AB3W2VZcXKyNGzdq2LBhSk1N7ajbAAAAxD3j9cqceIbskVMj3WzvvSbt+sP9V4vlzv6RzHGnyZz+bZnklNiEBTpISUnJngftIiPg1czRvfTNUVn6uqhWr60q18J1FaoPt10cW7q1Rku31uhe31Zdc3Q/HT5g57+jgekJumRiX22qCOrDrSG9X7BNXxXV6quiWg3OSNBxeWka0zdJDh2mAIADFJVi2+OPP666ujpdeOGFkqQlS5Zozpw5CoVCSk5O1uzZszVw4MBo3AoAAKDLMqnpMv97pewxJ8t9/J9SwbKWA1xX9o3nZT96R2bm/8hMmSbjeGITFoiixMREvfXWW8rOzlZiYuI+P98Yo1F9kjSqT5IuPayP3ltbqf+sKtPK7XVtjq9ucDUoo+1uuAHpCfrhQUM0tb9fb60u0yebqrWurF4PfF6kPsleHZeXrkP7pcjnoegGANg/UZlG+tFHH2nAgAFNX8+dO1eDBw/W9ddfr+zsbM2bNy8atwEAAOgWzOBhcn56u8yl10kZvVoPqKqQffjvcn9zrezKZa3PA12M4zgaOXKkDj74YDnOgf0IkuTz6KThGbrz5CG6+9QhOn1kplL9La85Niep3Y0XgmFXrrXKTvbpW2N66xfHDtAJQ9MV8Bptqw5p7tLt+u07G/Xm6nLVNZuOCgDA3opKZ1tJSYlycnIkSZWVlSooKNCNN96o8ePHKxgM6uGHH47GbQAAALoNY4zMEcfIjjtC9uWnZF+dL4V2WQx+/Wq5d/xM5vBvyJxzoUxWdmzCAnFqSGaivndYoi6YkK0PN1Tp9VVl+qKwRtOGZbT7nCeXbteCl9ZpaIZffZK96pviV/80vy49rK9WldRp4fpKldWF9cLyUr2+qkxHD0rVMYPTlJbI3nIAgL0Tle8Y1tqmRUWXL18ux3E0evRoSVJmZqYqKiqicRsAAIBuxyQkynzzf2Qnnyj3yf+TPv+w1Rj7yXuyiz+WOeUcmenflEnY92l4QCw1NDToySefVHp6uk466SR5vdEtXPk8jr4xJE3fGJKmrVVBZQbavn7YtXpjVZmKa0LaVN56CqpjpMxEj5L8HtWFXJXWSs9+VapXVpZpYr8UnTI8Qzmp7W/WAACAFKViW9++fbVo0SIdcsghWrhwofLz8+X3R74JlZaWKiWFBX4BAAB2x2TnyHPFz2W/WhxZz23XnUmD9bLP/lt2wX+kWZfInnZWbIIC+6GhoUHXX3+9JKmgoCDqxbbm2ps+KkmLC6tVXBNq97xrpe21YW2vDbc6t6G8RAvXVWjK4DQdPzRdgzISWpyvrA8rxe/IsMECAPR4UfkuN23aNN1///169913VV1drcsvv7zp3PLly1us5wYAAID2mVHj5Nx8t+zbL8s+92+pprrlgO3b5P6/27X19WcVHpwvDRomM2iolDNAxsNmCohPjuPopJNOUmJi4gGv2XYgPttcvedBu+H1GC3ZWqMlW2uUn5Wo44am6aDeAYWt9N15K+X3GPVN9qtPik99Gz/6JO98nOTj3ygA9ARRKbZNnz5dycnJWr58ufLz83XMMcc0nQsGg5o6dWo0bgMAANAjGI9H5oQZskccI/vsI7Lvvio1LtmxQ8PKZVLj5glWknx+acAQmcHDpIFDI5/7DZbx+Tr/BQC7SExM1L/+9S/l5uZqy5YtTUvQdLZLJvbRsXnpKmzwa8XmYm2tCmprVYO2VTWoumHPmyH84PAcfVVUo0Wbq1VQUqeCkjr1S/VpfE6yXCvVhazWlddrXXl9m89P9Tvqk+KPFN+Sfeqb6tdBtT71ccJK9seuCAkAiK6o9W9PnjxZkydPbnX8sssui9YtAAAAehSTmibzP1fIHnOy3MfvbSqutakhKK1ZIbtmhaTGApzHI/UbFOl8GzRMZtAwaWAea76hxzLGaHjvgI7JzdWWXE+Lol9VfVjbqhu0tapBW6sjRbgdH9uqG9QQtjq4T0Djc5N1yvBMvbO2Qh9uqNTmygatKtm+V/evDLqqLKnTqpJm68V9XKjfTR+sUdmBVuNrGsJ6bEmxMhO9ygh4lZHoUWbAq4xEr9ISPPI4TFkFgHgU1cUSNm3apGXLlqmyslLHH3+8MjIyVFJSopSUlKY13AAAALBvzKChcq6/XfbTBbJP/UsqKd67J4bD0oY1shvWSAvfiBTgjJH69o8U3gYPlRnYWIhLZo1d9GwpCR6lJHg0NKt1Mdpaq/L6sHyeSPdZRsCrM0dlaVp+uhauq9SzX5Uc0L0z2tnptLgmpOe+Lm3znGOktASPMpoX4hK9yghEjmUGvMrPSlSyn6mrANDZolJsc11X99xzj95+++2mY+PHj1dGRobuvfde5eXl6dxzz43GrQAAAHokY4zM4d+QHX+k9PUSpWzfqsr/fiG7ftXeF9+kyHTUwo2yhRulj99RU19P777SoKEyOzrgBg2VSc/siJeCHqi2tlbHHnusPB6P3njjDQUCrbu44pkxps2CWJLPo2n5GfrG4FS9taZCb68pV0ltSA1hq7CVEr1GwbBVWV3rDReaywi0XRArq939Zg5ldeHItcvanrb6u+mDNCo7qdXx6mBY8/67vbFIFynUZSX5lJgeVNi1omEOAA5MVIptTz/9tBYsWKD//d//1fjx43Xttdc2nZswYYLefvttim0AAABRYHx+mbGHKz03VzWNa1/ZygppwyrZdaul9atk16+Wtm3etwsXb5WKt8p+9sHOAlx6VqToNnhYpANu8DApK5vdFrHPrLXauHFjrGN0mESfR6eMyNT0/Awt2VqjN1eXa1NFUFKkA+34oek6pG+k6LVjWuqOKaq1YSngbXu9tj0V6fZkdx1z85a11Y23WlIkT4rfiXT7+Xd8OEpN8CjZ79FxeWnqlcR6kLFQFQwr2cf6fkC8i0qx7e2339bZZ5+tGTNmyHVbLizap08fbdu2LRq3AQAAQBtMapo0eoLM6AlNx2xtjbRhdaTzbf3qSAFu8wbJ7nkR+CblJdKXJbJffrqzAJec2tgBN1RmcL4aDpska/ihG7uXkJCgl156Sb1791ZCQkKs43QYj2M0ITdZ43OStHJ7nd5cXa4V2+u0uLBGiwtrdFB2QMfnpevk4RmRblVjdrtpRK8kr47NS4t0sNWGVFoXUkVdWHu7vUR7xbayuvY75iSpNuSqNuSqqKbtcYfmJrdZbNtaFdS1r6yLFOqaFemaHic0P+5pUdBL8JgeU8i31qqmwVVJbSjyURNS6Y7HtSFdO7lfm+vxWWt12TOr5HWkoVmJystM1NCsBA3NTFROiq/HvH9AVxCVYltJSYlGjBjR5jmfz6e6uro2zwEAAKBjmECSNGKMzIgxTcdssF7atE523aqdHXCb1kqh3f/g3UJ1pfTVYtmvFstKKrxXUkaWzNjDZcYeIY0aK+PvvsUU7B+Px6Px48fHfDfSzmKM0YjeAY3oHdCG8nq9tbpcS7bW6OuiWn1dVKuB6X6dMDRdh+Qk7/Y6B/dJ0sF9Wk4DDbtWFfVhldVFCjTNC3E7HpfVhVTb4CrQTgdU6W6mp+6NlHbWgausd1VZH1ZlfVhSwz5d0+sY/fqEga1erxTZKOKFr0sV8DlK8jlK8nsin32OAj5Hyb7I1/44KdhV1Ye1pqxOJTWR4lnzQtqO4lp9uP1/A987LKysQOsf1bdW1qsyGOl2XLS5Wos2VzedS/I5ysuMFN6GZiVqaGaCBqQnyMucYCAmolJsS09Pb7d7bfPmzcrKyorGbQAAAHAAjD9Byhshk7fzl6Q2FJK2bGjWAbdK2rBGqt+HX5aWlci++6rsu69Kfr80anxj8e1wmQz+PxA928D0BH13Qh8V1zTo7TUV+nhjlTaUB/XA50XKTi7TyWOMEsI16hXwqFeSb4/FEY9jlBmIbICQt5/LKmYFvJoyOLWxSBcp3FUH977rNSWh7SJeVXD/p72GXNv+dNrasP69ZM9rU3qMGgtwO4txST5H3xmXrWFtbHwRdq3+u61GST6Pkv1OUzHP57Qu2tWF3EjRrCak7Y0FtBkjM9vsQPuqqFa3vbP/06ZLakJtFtuWb61s9zk1Da7+u61W/91W23TM5xgNzkho6n47amCqMtq4LoDoi8q/tAkTJujpp59u2hRBivw2p6amRi+//LImTpwYjdsAAAAgyozXKw3MkxmYJ02OHLNuWNq2pbEDbudUVNVU7fmCwaC0+GPZxR9HproNzpcZd4TMuMOlgUPjousEnS8UCun5559XRkaGjjnmGHk8PW+HzN5JPp1zcC+dlJ+h99ZVaOH6ShVXN2juZxvV0NAgq8j6bhmJXvVO8qp3sk/ZSV71TvKpd7JXWQGffJ7o/PsZm5Ossbt01TW4VskZvbVqwxZV1odUFQyrKuiqKhjpVKtu/Lo6GG63KHYgxTap/Y65moa9KwSGrVQZdFW5S+HwrNG92hxfFQzrl29saHXc60Q2v0jyOUrwr1NxZZ2q28gwZXBqm9Nps5IO7Mfs9joPVxTtxX+Dm2lwrQpK6lRQUiepXMN7BSi2AZ0kKv/SZs2apc8//1zXXHONDj74YEnSY489pg0bNsjj8eicc86Jxm0AAADQCYzjkXIGyOQMkI6cKimyVpC2b2uafmrXRzZjUHnp7i+2rkB2XYHsc49KWb13Tjc96BAZn78TXg3iQTAY1FVXXSVJKigo6HK7kUZTaoJHp47I1AlD0/XxpiptC3q1dluZiqsbVB+2TVMNV2xv2V3a0YU4v8dR75QENWQkyNr9+7c5tm+Sbj1hYItCXVV9s8dNH5Gvd+2ma69jrqbhwIp47U2nba+IF3KlivqwKvYwHbakNtR2sW0fClopfkdZAa+yGrsVswJe9Ulpex3MS44aogm9jFZtr9Pq0jqtLqnT6tL6xpy75zHS4Iy2/1xXbq/VnHc3NU4/TVReYydc7yRvp/+CxFqr2pAra6Xkdoqvr68qU8H2OtU0uKoLucpMLZHfNigtwVFagldpiR6lJ3iUluBRWqJXKX5HDr/oQSeLSrEtIyNDt99+u5544gl9/vnnchxH69at06GHHqpzzz1XKSkp0bgNAAAAYsQYI/XuK/XuK3Po0U3Heofrte31F+Uu/lha+dXuN2AoKZZ9+2XZt1+WEhIj003HHS4z9jCZtP2cD4cuwRijb3zjG0pISKC7sVGC19ExQ9Kb1rFz3ch6Z8U1IRXVNKi4OqTiZp/3pRDXO8mr7A7oiNuTtESvxubs/Y+YYTeyUcCOIlx7HXPJfo+OHJCi6gZXtQ1h1TS4qgm6qmlw1eDuef2/ZP++Fdv2VklNSGqjaS4twaPUhEjBZ0chLSvJu/NxY2EtM+BVQjuvuS2OMeqflqB+qX59Y0iapEhxanttqKnwtrqkTmtK67StumV33MD0BPk8bd9rdUm9impCKqqp0kcbd3bPpSV4Wq4DlxW5t2cv/w2vLa3T9pqQqhtc1TREiqs1DZHuyJpdjtU0hBv/fF25VjpmSJqundyvzet+sqlKH25o3uW3+44/x0ipfo/SEj3qn+bXjccMaHNcZX1YwbCrtARvp/2bQfcVtR7SjIwMff/734/W5QAAANAF+AYMkXPSWTLTZ8pWVcguXSQt/kT2v59JtTXtP7G+TvriQ9kvPpQ1RhoyfOd00/5DKMh0M4FAQHPnzu0xGyTsD2OM0hK9Skv0augu64tZa1UZdFVc3bDPhTgjKTPgiXTBNRbgYlGIa4vHMUptLEztzrCsRP18atsFkoZwpECzo1BT0+CquiHc9LimIVI8aUvItUpN8KgmGNZu9itoIeB1lJW0+0KZxzF65Jzhe3fBA2SMafqzPWJAatPxivqw1jTrfstpp1tOklaXtr1GZ0V9uGkn3R0SvUZDMhPVO22bSiprNDE3ReeMaXua7n2LtunLrbv5PrAbNbuZkpzk27dp6K6VyuvDKq8Pa3d/219aUapHG9cGTPI5kc64BI/SEz2RjrkET7OuuWYddImefc6E7o8J2wAAAIgKk5ImM+k4adJxsqEGaeWyyNptSz6Rigrbf6K10poVsmtWyD7ziNSrT2S66bgjIjuq+tr/IRHoCYwxTT/476kQt70mpKLqBhXXhJpNTQ2rpDa8+0Jcsk99NodUXlEht7FbzMrK2sg/UUlyIwcjxxU5bpsd29Fk1nS8MZ/d5ZjbeMEdx2zjF01Nakbq16tWvlCtMhJ3dIF5lJHYusDl8zjyeRyltd7/YI9G9g7okXOGy1qrBtc2dcvt6LSqDVklp6VLtZVNGbpKUSUtwaNxOckat4cdbyVpdcneb4hTF7L6uqhWKopsxJDdxjTaHZLamb67N3bXdZh8ANdNS2y/BFLebDrujr8HhVV7t6tun2SfXryi7U489Ez7XWx76qmn9mk867YBAAD0HMbrk0aNkxk1Tvbc70V2PF38ieySj6VVy3c/3XT7Ntm3XpR960UpISAdPCEy3fSQw2RS0zvvRQBdwJ4KcVVBt7H4trMAV9RGIW7l9jr5ttQ1bdYQS0bS2vLtbWZJ9jnKaJyKmZHoaZqOmdlYlEvxO/vcGWuMkd9j5A84ygi0PJ6b21dbtrjduhvzh5NyVVCyowuuTmtK6/d6iu3u1tNrb/ru3mhrQ4odRvUJKBi2jTvPOnISkrR5e7nK60JNa+2V14VVF2p9jfTddFFW1u3/2oDtrQuInmu/i21PPvnkPo3f22LbsmXL9Nxzz2nNmjUqLS3VddddpyOOOKLpvLVWTz75pN544w1VVVVp+PDhuuSSSzRw4MCmMQ0NDXr44Ye1cOFCBYNBjRkzRt/73vfUq1fb7a0AAADoOMYYqd8gmX6DpFPOlq0sl/3yU9nFn0j//Vyqr23/yfW10mfvy372fmS66dCRkemmY4+Q+g1kumkXUVtbq1NPPVVer1fPPfdcj94goTMZs3OaZnuFuOKaBhVVN2h7bViB5FRVVVbIWtu0oLwxkeJX5LPRjn9yTuNn03ifnY9bHmsat+O4Ig+cxifsONb8ulZGTmKK1hQWq6Q2pNLakMpqQ6oNWVU3uKpuCGpTRbDN1+x1jDIDHmUmepXRWITLCHgai3ORj3hbj6shbFUfclUbclUfclUXsqoLubt8WGUWhuVrqFGvpMj6fGkJnqj8N3BQRoIGZSTo+KGRX2a41mprVUPjJgz1TdNRS9soRu26yUVzGYle9Qp4leR3lOTzKNnnKMnvKLlxp9ddH6f4PI1jnXY3R5CkyYPSNHlQZM26SEG07enpwbAbKb7VRaaQVtSFlLmbzSsq6tveBXZvpCe2n3fhugq9s7ZCRw9K1eH9U3b72tB97Hexbe7cudHM0aS+vl5DhgzRcccdpz/84Q+tzj/77LN68cUXdcUVVyg3N1dPP/20brvtNt11111N37QfeOABLVq0SD/+8Y+Vmpqqhx56SL/73e80Z84cOQ4VZwAAgFgyqekyR58gHX2CbEODtGLpzumm27e1/0RrpVVfy676Wvbph6TsnGbTTQ/uvBeAfWat1YoVK2IdA800L8TlZSbutmgRi2y5ubna0kststQ2uCqrixTfSmtDKq0LqbQ2HCnGNXY1hVyrouqQiqrbL5ykJniU1TgttXlX3I6pogHv3nXHhV2r+nBjcaxh1+JYs6JZg6u6cOPnVoU0q9BebPJgJPl8NS26/fweE9kUI8kXKcDt2Bwj2ae0BM9+78DpGKPcVL9yU/2aPGjn8ZLGjRjWlweVlJyicH21+ia3P430ggl9dMGEPvuVIRr8Hke9kxz1bmOqq7WR973BtQqGrRrCVt+b2Ffl9ZG/U+X1IVXURbrkIjvqhlXV4KomGFZtyFVtyKq+wdWOUuPWqgbd9upXSnVC6p/qU7/G9y/B6+idtRX6aGNk8wmvI43PSdZRg1J15IDUPa5XiK4r7tZsmzBhgiZMmNDmOWutXnrpJc2cOVNHHnmkJOnKK6/UpZdeqgULFmjatGmqqanRm2++qR/+8IcaO3asJOmHP/yhLr/8ci1ZskTjx49v89oNDQ1qaNg5H9sY01S86+m/MW36TVUcvA9kid8czTOQpaV4yRIvOZpnIEtL8ZIlXnI0z0CWluIlSzRyGL9fGnOoNOZQ2e9cJm1aJ7v448jupmtW7Fwsqi1FhbJvPC/7xvNSIEnFh02WO3CozJAR0sC8mK31Fi9/Ps0zxDpLYmKi5s2bp6ysLCUmJsY0T7y8J80zkKWl9rIk+T1K8nvULy2hzeeFXauyukgXXGldWCW1Ox5HinMltSGFwjZSPKkPa73a7o7ze01jAc6nnKxalZRXqq5Z51ltQ+RzcG93VdjT621230Sv08aHUcDnUWJystZuLVVRdYNKa0NqCFttqWzQlsrW64p5HdOsAOdTdrK3cSOFSMff/hTieiX51CvJpyMGGuXk5KiwsLDTCrPWWtWGXFUHI7vXVgd37mqasNXV9tIyBcNuU+GsIWwVdFt+3RB2FXR3fr0/Aj6PAo3r9tnGXDsutWJrVYuagiRlBrxatHnnbqkhV/p0c7U+3Vytv5tCHZKTrKMHpWrSwFRl7GY9uX3RFf4t9wTGxvrXFrsxa9asFtNIt27dqh/+8IeaM2eO8vLymsbdcccdSkpK0lVXXaWlS5fq17/+tf7v//5PKSkpTWOuv/56HX744Zo1a1ab93riiSdarEOXl5enOXPmdNArAwAAwJ6ES7er9pMFqvv4PdV99qFs/d4v4i2vT/5hI+UfcbD8I8fIP3KMvLkDeuT/8AOIsNaqqj6k7dVBFVcHtb26PvK4KqjtjV9X1O37VEKvxyjJ51HA74189nkU8HsU8DlNxZkdx3aMa3HOH/m8LwWwUNhVcXVQWyvqtLWyXlsrd3yuV1FVfdMmF23mdYyyUxPUNzVRfVMT1Det8XNqonol++VxOv6/k2E38mdRWR9SZV2DKup2Pq5sehxSZX3kXFV9SOG96ADcHx7HyO915Pc4TZ8TvJGNNxKaHd9xrNX5xjFha7WxtFbrS2u0vrRGZTUNqqhr0KbyPX/vMpIOHZih40f00XEjspWd0nZBGV1H1Drbli1bppdfflmbNm1SMNjyNwTGGP3lL3854HuUlZVJktLTWy6Mm56eruLi4qYxXq+3RaFtx5gdz2/LzJkzNWPGjBaZJamoqEih0P7P3e4OjOn831yQpevlIEv8Z4mXHGSJ/yzxkoMs8Z+lU3IccoR0yBFyvhuU/XpJZLrp4k+k0uLdPy/UoODypQouXyo937j8SXKqTN5wKW+kzNARMkOGd8iGC/Hy50OW+M5BlthlSZDU3yf1z5CU4ZPkkxTZtbMh7KqsbsfU1LA8gRTVVVcqwRPpOgt4jRJ9O7vOEryOvHtdnAo3fgSbHjbUSQ2SKvbwzPbekz4eqU+GdEiGX5JfUqrCrlVpbajFphjFNZGvt9eEVNtgtb4+qPXFla3u4xgpK2nndNTITrWRrrisgFcex7SZpSHsqqqp6yysqsbOsx2Pq4Lhpo60qmBYtXu5AcOu/F6jFJ9HyX6PUvyOkhM8ys5IV11NlXyOkc9j5Pc48nkij31OZPMLn8dp9jgyxu8x8jrmAIqLVjv+IJvek0ChbG6qpFRV1oe1anutFqyv0Jdba1RY2f7mI1bSog1lWrShTL9/Y4XyMhN0zJB0nTAsfZ873nrSv+VY8Hq9ys7O3vO4aNzs66+/1q233qrRo0dr06ZNGj9+vGpra7VixQr17dtXI0eOjMZtmuz6G8m9+UPb0xifzydfO1MNustfigNlrY2b94Is8ZtDIkt74iVLvOSQyNKeeMkSLzkksrQnXrJ0Sg6vT2bMRJkxE2W/8wNpwxrZJY2Ft7Ur9+4a1ZWySz+Tln628wee7ByZvJFS3nCZvBHSoKEyPn9UIsfLn48U+yyhUEivv/66srKyNHHiRHk8sV+nKNbvSXNkaVsssnidHeugeZutY7f7nwk7M+PevCeOkXoledUryatdfxJ3baQQt72mrWJcqHHNu8imGV8V1ba6bmYgUnhLTanQttJKVQVDqg66qt+PaZlGatoMIdnvKGVHAc0f2Uwhxe9RSkLjucYC266bW0RjrcFo/vk1//NJ8Tsal5uscbmRQm51MKwPN1Tq7bUVWratRm1sltpkTWm91pRu06ebKjWyd0D90/zqn+pXvzT/Xm+M0dP/LcdaVIptTzzxhI499lhdeuml+va3v61zzz1XQ4cO1bp16/Tb3/62xW6iByIjI0NSpHstMzOz6XhFRUVTt1tGRoZCoZCqqqpadLdVVFREvegHAACAzmeMiRTFBg2VZpwnW1YiffmpEjetVc1/v5AKN+79xYoKZYsKpY/fiRTgPF5pwBCZoSMiHXB5I6Q+uTJssnVAgsGgLrnkEklSQUEBu5ECMeIY07T22gi1/HfoWquKurCKmhXhtteEIl9Xh9TgWm2vCamkJiRfeajFZg2S5DFqLJhFCmTNi2g7i2mRQlqyP7ILaWdMWY0XyX6PThiWoROGZai2wdVnm6u0cH2lPt1UqfrWG73KY6S6kKvFhTVaXFjTdDzF7yg31a8BaX71T/OrX6pf2cm+HvVedgVRKbZt2LBBp59+etPXrhsp0Q4ePFhnn3225s2bp8MOO+yA79OnTx9lZGRoyZIlTWu2hUIhLVu2TOeff74kaejQofJ4PFqyZImOPvpoSVJpaanWr1/fNAYAAADdh8nIkjnmJPXKzVVwyxa51ZXS2gLZNStk16yQVi+XKsv37mLhkLSuQHZdgfTWS5EfJJOSpSEjGqeejpCGjuiQ6afdmTFGhx12mPx+P+vmAXHKMUYZgcgGCsN7tTxnrVVFfVjFNZGuuMysTAWrKpTsM5HuM59HiV7Dv++9FPA5mjw4TZMHp6k+5OrzLdV6f32lPt5YpdrGlrfjh6Zpen6mNlUEtbkyqE0VQW2rblBV0NWiTVV6c3W4qZCZ5IsU4Pqn+tU/PUFHJqYrNlsEYYeoFNvq6+uVmJgox3Hk9XpVWblz7ne/fv20cePe/3axrq5OhYWFTV9v27ZNa9euVUpKinr37q1TTz1V8+fPV25urnJycjR//nwlJCRoypQpkqSkpCQdf/zxevjhh5WamqqUlBQ9/PDDGjRoUNPupAAAAOi+TFKKNHq8zOjxkhqnCJUUya5eIa1ZLrtmpbS+QAq2vRNhKzXV0rLPZZd9vrOLo3ffSNdb3oid00/9LGjdnkAgoOeee+6Ap3sBiA1jjNITvUpP9Cq/l1Fubra2bAnxbzkKEryOJg2M7EjaEI50si1cX6kThqZrRO+ARvTe2YHYEHa1papBd763SUU1kR12S2oln2O0rapBBdvrlOB19PyKCh2em6gZIzOV4KUzOxaiUmzr3bu3yssjvy0cMGCAPvvsM02YMEFSZOOEXTcr2J1Vq1Zp9uzZTV8/9NBDkqSpU6fqyiuv1JlnnqlgMKj77rtP1dXVys/P10033dSiFf2CCy6Qx+PRn/70JwWDQY0ZM0Y33HCDHNr/AQAAehxjjNSrj0yvPtLhkV/Q2lBI2rwuUnhbszxSiCvcKO3tD47FW2WLt0qfvNc4/dQj9Y9MPzV5IxSacrwk/t8TALD3fB5Hh/VP0WH9266h+DyO0hM82lDR8pdFDa5VaV1YpXVhJfkcBXyu3qkPanlxrb49trfyMhM7Iz6aiUqxbfTo0frvf/+rSZMm6YQTTtD999+vTZs2yefzafHixS12+dyTgw8+WE888US7540xmjVrlmbNmtXuGL/fr4svvlgXX3zxPr0OAAAA9AzG65UGDZMZNEyaerIkydbWSGtX7px+umaFVF66dxcMh6X1q2TXr5J9+2Vt+dfdMpNPlDnvezKJSR34SgAAPcmnm6rk7ub3QjUNrmoagiqpkcpqQ7r7gy06cVi6TsrPbLXBBDpOVIpts2bNUlVVlSRp+vTpCgaDeu+992SM0VlnnaWzzjorGrcBAAAAOowJJEmjxsmMGiepcfppabG0ZoXs6hWya1dIawukYP1eXc8ufF125X/lfO86mbzhHRk97tXW1urss8+Wz+fT3LlzlZhIlwUA7I9j89KVm+rX++sr9f6GSm2vCbU5zkoqrw+roj6sp5Zu15dba/Td8X3UPy06O25j96JSbEtLS1NaWlrT1zNmzNinbjYAAAAg3hhjpKxsKStbZuJkSZINh6XN65s63+yaFdLm9e1PP922Re6cn8qc+T8yJ83ssbuaWmu1ePHipscAgP3jcYxG90nS6D5JunhiH63cXqcPGgtvW6saWo23kkrrwvp4Y5UKttfpnIN7aXp+BruXdrCoFNvaUlxcrI0bN2rYsGFKTU3tqNsAAAAAncZ4PNLAPJmBedIxJ0mSbF2NtG5VpPttzXJp2WKpvnbnk8Jh2acflF32uZyLr5HJ7NXO1bsvv9+vhx56SFlZWfL76aoAgGhwjNHI3gGN7B3QBROytaa0Xm+uKderK8sVDLstxrpWKqkN6dWCMq3YXqfvjO2t7GT2LO0oUSm2Pf7446qrq9OFF14oSVqyZInmzJmjUCikpKQk/frXv9bAgQOjcSsAAAAgrpjEJGnkITIjD4kcKN4qz4N/VvDrL1sO/HqJ3Nk/knPBD2UmTOr8oDHk9Xp14oknshspAHQQY4yGZiVqWK+Avj91lP78+n/1xuryFuu7TR2SpqLqBq0rq9edCzbr9IMydfSgVDmGLrdoi0of+0cffaQBAwY0fT137lwNHjxY119/vfr06aN58+ZF4zYAAABA3DPZOeoz558yM86VzC7/u11dKffvv5X78N9l6/du7TcAAPZFTlqifnhUP/35tDwdNTAy0zAj0aMfHJGj66f014heiWpwrZ5eVqJ7P9mqstq2133D/otKZ1tJSYlycnIkSZWVlSooKNCNN96o8ePHKxgM6uGHH47GbQAAAIAuwXi98nzzf+SOGif3vj9GNlpoxr77SmTzhEuvi0xJ7ebC4bAWLlyoXr16adSoUXJ66Np1ANCZBqYn6GfH9NfK7bUqrQ0p0eso0evo+4f31fvrK/X816Vasb1Ot7+7USHX6tLD+uqgbHbQjoaofJez1ja1gi9fvlyO42j06NGSpMzMTFVUVETjNgAAAECXYkaMkXPLn6WJR7c+uWWD3N9eK/f157r9tMr6+np9+9vf1vTp01VPRx8AdKrhvQI6YsDOtfQdYzRlcJqum9JPgzMStKWyQQUl9brhtfW69a0NWl/Of6cPVFSKbX379tWiRYskSQsXLlR+fn7TwqelpaVKSUmJxm0AAACALsckp8i57AaZ714l+RNangyFZOfeJ/fPv5atKI1NwE5gjNHo0aM1bty4yC6vAICYy072adaYLFUGw03HPt1crR+9sEZ3f7BF29rY3RR7JyrFtmnTpunll1/WxRdfrPfff18nnHBC07nly5e3WM8NAAAA6GmMMXK+MV3OL/8kDRrWesDSRXJ/9SPZLxd1frhOEAgE9Prrr+uLL75QIBCIdRwAQKMXlpe12ERBkqykN1eX6wfPrdJ9i7aqvI413fZVVNZsmz59upKTk7V8+XLl5+frmGOOaToXDAY1derUaNwGAAAA6NJMzgA5N94h+8wjsq/Ob3myslzun2fLnHC6zNkXyPj8sQkJAOgxLpiQrT7JPj25tFiVQbfFubCVnv+6VP8pKNfMUVk6Y1SmknyeGCXtWqJSbJOkyZMna/Lkya2OX3bZZdG6BQAAANDlGa9P5pyLZEePl/t/d0nlLaeP2jeel12+VM6l18r0GxSbkACAHsHvcXTmqCydOCxdz3xVoue+LlFdqGWrW13I1WNfFuvFFaX61pheOmV4hnweNrrZHd4dAAAAIAbM6AmRzRPGHt765MY1cn/zE7nvvNItNk+ora3V2WefrWOPPVa1tbWxjgMA2EWy36Pzx2XrnjOGacbITHnaWF6zoj6s+xdt0+XPrdYbq8oU3nX+KZpEpbPtyiuvbHehU2OMkpOTNWzYMJ1yyims3wYAAAA0Mqnpcq76hezbL8k+8X9SqNli1MGg7CN/l126SM53fyiTmha7oAfIWqsPPvig6TEAID5lBLy69LC+OuOgTD22pFhvraloNaaoJqQ/f1ioldvr9IMjcmKQMv5FpbNt9OjRstaqpKRE2dnZys/PV3Z2tkpKSuS6rnr16qWPP/5YN954o1atWhWNWwIAAADdgjFGznGnyfnFH6X+g1sP+OIjub/+kexXizs/XJT4/X7dc889euKJJ+T3sxYdAMS7vil+XX10P/35tDxN7Jfc5piTh2d0bqguJCrFtnHjxsnn8+nPf/6zbrnlFl199dW65ZZb9Oc//1k+n0+HH3647r77buXm5uqJJ56Ixi0BAACAbsX0Hyzn53fKHD+j9cmyErl/ulnuvAdlm3e/dRFer1enn366vvWtb8nrjdqy0QCADjY4I0E3HzdQc6YP1uCMnb8sSU3waHVpvVy6ldsUlWLb/Pnz9a1vfUu9e/ducbx3794655xz9OyzzyopKUmnnXaaVqxYEY1bAgAAAN2O8SfI+fb35Vz1Sylll2mj1sq+Mk/u726Q3bo5NgEBAD3SQdkB3X1qnq6fkquMRI8yEz16YXmp/vZRoYqqW/4SaNm2Gm2sqI9R0vgQlWJbYWGhkpKS2jyXnJysbdu2SZKys7MVDAajcUsAAACg2zLjDo9snjB6fOuT6wrk3nq13Pff6DLrn4XDYX388cdauHChwuFwrOMAAPaDMUZTBqfrgbPy9T/js5XgMVpTWq8/LNyshesrZK1VyLW6+4Mt+uELa/TXD7doa2VdrGPHRFSKbb1799bbb7/d5rm33nqrqeOtqqpKKSkp0bglAAAA0K2ZjCw5P/6VzLcukjy7TL2sr5P9192y/7xTtqYqNgH3QX19vb75zW9qypQpqq/v2d0OANDVGWN05IBUXf+N/srPSlQwbDXvvyW699Oteu6rEhVWNci10msFZfrda8tjHTcmorJgwumnn65//vOf+uUvf6lJkyYpPT1d5eXl+uCDD7Ry5UpddtllkqSlS5dq6NCh0bglAAAA0O0Zx5GZPlN25Fi5/7xT2rqpxXn7yXuyq76W871rZYaPjlHKPTPGKC8vTx6PJ9ZRAABRkhXw6gdH9NWCdZV6YXmpvi6q1Wvl5S3GXDo5T7LVMUoYO1Eptp144omSpCeffFIPPfRQ0/GMjAxdeumlOv744yVJZ511lnw+XzRuCQAAAPQYZvAwOb/8k+zc+2Tfe63lyZIiub//ucyMWTKnnSsThwWtQCCghQsXKjc3V1u2bOky018BALvnGKNjhqRpZO9EPfxFkUprQyqrC8tKOnJAikbnpGnLFopt++3EE0/UCSecoM2bN6uyslKpqanq16+fjDFNYzIyMqJ1OwAAAKBHMQmJMt+9SvbgQ+U+9Fep+fRR68o+/7jssi8iXW69+8YuKACgx+mb4tc1R/fTG6vL9fKKUhXXhFReF9ZnG0qV2wM3oY7Kmm07GGPUv39/HXTQQerfv3+LQhsAAACAA2cmHi3nlrulEWNan1z1tdxf/1jux+92fjAAQI/mcYym52fo2sn9dEjfJIVcq7veKlBhVc/bKHO/64vLli3T0KFDlZiYqGXLlu1x/OjR8buGBAAAANCVmKxsOdfeKvvyPNnnHpVcd+fJ2hrZf94pd+lncs6/LHYhm6mrq9P3v/99JSQk6K9//asSEhJiHQkA0EEGpCfomqP76ZWCMqWkpConxdfjlg/Y72Lb7Nmz9Zvf/Eb5+fmaPXv2HsfPnTt3f28FAAAAYBfG8cicNkv2oLFy7/uDVLy1xXn7wZsKF3yl+htvl9J6xShlhOu6euONN5oeAwC6N5/H6IyDspSTk6PCwsJYx+l0+11su+WWWzRgwICmxwAAAAA6nxl2kJyb75Z99B+yH77d8mTRFm27/hI5M78rTTtTxonqKjJ7zefz6U9/+pMyMjLYMA0AepCeurzYfhfbmk8LHTFihEKhkBITE1uNq6urk9fbA1fDAwAAADqJCSTJXPITuQdPkP33P6S62p0nw2G5T/1LWv6lnIuvlklJ6/R8Pp9P5557LruRAgB6hKj8auuee+7RP/7xjzbP3XvvvbrvvvuicRsAAAAAu+FMOk7OzXdLQ0e2Pvnlp3J/fbVswVedHwwAgB4kKsW2//73vzrssMPaPDdx4kR9+eWX0bgNAAAAgD0w2Tlyrr9d5tRZ0q7Td0qL5f7+RrmvzJPtxLXTwuGwli5dqi+++ELhcLjT7gsAQCxEpdhWXl6uzMzMNs9lZGSorKwsGrcBAAAAsBeM1ytn5v/IuXq2nIysliddV3beg3L/cqtsZUWn5Kmvr9f06dM1YcIE1dfXd8o9AQCIlagU25KSktrdXaKwsFCBQCAatwEAAACwD5yDJyjnL4/KjDyk9cmli+T++seyK5d1eA5jjHJyctSvX78OvxcAALEWlWLbwQcfrGeeeUZVVVUtjldVVemZZ57RmDFjonEbAAAAAPvIk9VbzrW3ypx+XutppWXb5d75c7kvP9Wh00oDgYA+++wzbdq0SUlJSR12HwAA4kFUtgmdNWuWbrzxRv3oRz/S0UcfraysLG3fvl0ffvihQqGQZs2aFY3bAAAAANgPxvHIOeM7ssMPlnvfH6SKsp0nXVf26YdkVyyVc/E1MqnpMcsJAEB3EJXOtn79+mn27NkaMmSI3njjDc2dO1dvvvmmhgwZol//+te0iwMAAABxwIwaF9mt9KCxrU8u/SwyrXTF0s4PBgBANxKVzjZJGjJkiG6++WYFg0FVVVUpJSVFfr8/WpcHAAAAEAUmPVPONbNlX3xS9vnHJGt3niwrkXvnL2TO/I7MKefIOFH53bzq6ur04x//WImJibrjjjuUkJAQlesCABCPovPdsxm/36+srCwKbQAAAECcMo5HzunnyfnJrVJ6ZsuT1pV95hG5d8+WbT7d9AC4rqsXXnhBTz31lNwOXBsOAIB4EPViGwAAAICuwRw0Vs7Nd0mjxrU+uexzub++Wnb5gU8r9fl8+s1vfqO//vWv8vl8B3w9AADiGcU2AAAAoAczaZlyrv6VzJnnS2aXHw/KS+T+4RdyX3hc1g3v9z18Pp8uuugiXXnllRTbAADdHsU2AAAAoIczjkfOjHPlXHublJ7V8qR1ZZ99VO5dv5KtKI1NQAAAuhCKbQAAAAAkSWbkmMi00tETWp/8anFkWunXS/b5uq7ravXq1Vq5ciVrtgEAuj2KbQAAAACamLQMOT++Reab/9PGtNJSuX+8We5zj+3TtNK6ujpNmTJFI0aMUF1dXZQTAwAQXyi2AQAAAGjBOI6c02bJue42KaONaaXPPyb3T7fIlu/9tNK0tDSlp6dHOSkAAPGHYhsAAACANpkRY+TcfLc05tDWJ79eIvfXP5b9avEer5OUlKSvv/5aZWVlSkpK6oCkAADED4ptAAAAANplUtPl/PBmmbMukJxdfnyoKJP7p5vlPvvoAe1WCgBAd0KxDQAAAMBuGceRc8rZcq77rZTRq+VJa2VfeFzuH2+WLSuJTUAAAOIIxTYAAAAAe8UMHx2ZVnrIYa1PLv8yMq102eetTtXX1+vqq6/WhRdeqPr6+k5ICgBA7FBsAwAAALDXTGqanKt+IXPOha2nlVaWy73rV3KfeUQ2vHNaaTgc1hNPPKEHH3xQ4TDTTQEA3Zs31gEAAAAAdC3GcWROOkt22Ci5//y9VFK886S1si8+IbtymZxLr5XJ6CWv16tf/OIXSktLk9fLjyAAgO6NzjYAAAAA+8Xkj5Lzy7vanla6YqncX18tu/Qz+f1+XXHFFbr++uvl9/s7PScAAJ2JYhsAAACA/WZSdkwrvUjyeFqerCyXe/ev5M5/uMW0UgAAujOKbQAAAAAOiHEcOSfNlHP97VJWdqvz4Ref0KZfXaN1Xy6W67oxSAgAQOeh2AYAAAAgKsywg+TcfJc07ogWx+vCro64/ykNGTte1f++R3Z7UWwCAgDQCSi2AQAAAIgak5wq58qbZGZd0mJaqddEPuwbz8v9+aVy7/297JqVMUwKAEDHYCsgAAAAAFFljJGZdqbssIPk3vt7JW3fptWnTNw5wHVlP3lP9pP3pPzRcqZ/Uxp3uIzjafeaAAB0FRTbAAAAAHQIM3SknF/eJfvYPbIfvytZ23pQwTK5BcukPrkyJ54hc/QJMgmJnR8WAIAoYRopAAAAgA5jklPkfO9aeX5zj1JOP1dqr5C2bYvso/fI/enFcp9+ULZse+cGBQAgSii2AQAAAOhQ9fX1+vmf/qxffLlWoVvvkTnrAikjq+3BNVWyL8+T+7NL5d7/J9n1qzs3LAAAB6hLTiMNh8N68skn9d5776msrEyZmZk69thjddZZZ8lxIvVDa62efPJJvfHGG6qqqtLw4cN1ySWXaODAgTFODwAAAPQs4XBYDz74oCTpJz/5iZxTzpaddobspwtkX3tG2rCmjSeFZD98S/bDt6SDxsqZdqY0ZqKMQ78AACC+dcli27PPPqv//Oc/uvLKKzVgwACtXr1af//735WUlKRTTz21acyLL76oK664Qrm5uXr66ad122236a677lIgEIjxKwAAAAB6Dq/Xq5/85CdKTU2V1xv5EcR4fTKTjpM98lhpxVK5rz0jLfmk7Qt8vUTu10uknAEy086QmXScjD+h0/IDALAvumSxbcWKFTrssMN06KGHSpL69OmjBQsWaNWqVZIiXW0vvfSSZs6cqSOPPFKSdOWVV+rSSy/VggULNG3atJhlBwAAAHoav9+v6667Trm5udqyZYtss40SjDHSyEPkGXmIbOFG2f88J/vBm1JDsPWFCjfKPvx32fmPyBx7qsxxp8ikZXbeCwEAYC90yWLbQQcdpP/85z/avHmz+vXrp7Vr12r58uW64IILJEnbtm1TWVmZxo0b1/Qcn8+n0aNHa/ny5W0W2xoaGtTQ0ND0tTGmqQPOGNPBryi+7Xj98fA+kCV+czTPQJaW4iVLvORonoEsLcVLlnjJ0TwDWVqKlyzxkqN5BrK01NWymNyB0nevlJ35P7LvvCL3zRekirLWA6sqZF94XPaVp2QmHSdn2pky/QdHLUdnIUvb4iVLvORonoEsLcVLlnjJ0TwDWWLLWNvW/tvxzVqrxx57TM8++6wcx5HrujrvvPM0c+ZMSdLy5cv1y1/+Uv/4xz+UlbVz4dV77rlHxcXFuummm1pd84knntBTTz3V9HVeXp7mzJnT8S8GAAAA6OastSovL5ckpaen7/UPXjZYr+p3XlXV/H+rYd2q3Y5NPPQopZ51vhLGH9kjf7ADAMSPLtnZ9v777+u9997Tj370Iw0cOFBr167VAw880LRRwg67fpPdXV1x5syZmjFjRqvnFhUVKRQKRfcFdDHGGOXk5KiwsHC37yFZenYOssR/lnjJQZb4zxIvOcgS/1niJQdZ4j9LTU2N8vPzJUmrVq3atzWUxxwue/BhcpZ9Lvvas7L//azNYXWffaC6zz6Q+g+WM+2bMkdOlfH5Wo2Ll/eELPGfJV5ykCX+s8RLDrJ0PK/Xq+zs7D2P64QsUffII4/ozDPP1OTJkyVJgwYNUlFRkZ555hkde+yxysjIkKSmnUp3qKioUHp6epvX9Pl88rXxzVjafZGuJ7HWxs17QZb4zSGRpT3xkiVeckhkaU+8ZImXHBJZ2hMvWeIlh0SW9sQ6S/N7728WM3qCzOgJspvWyf7nWdmP3pba+qX4pnVyH7hbevpBmeNOk5l6ikxqWpuZ+PNpjSzxm0MiS3viJUu85JDIEmtdct/s+vp6Obts+e04TtMfXp8+fZSRkaElS5Y0nQ+FQlq2bJlGjhzZqVkBAACAni4QCGjdunUKBoP71tXWBtN/sJwLfyRnzv0yM86VUlLbHlhRJvvsv+X+7GK5j/xdtnDjAd0XAIC91SU72yZOnKinn35avXv31oABA7R27Vq98MILOu644yRFWhVPPfVUzZ8/X7m5ucrJydH8+fOVkJCgKVOmxDg9AAAA0LMYY5pmkhhjotLhYNIyZc48X/aUc2Q/fEv2P89KhZtaDwwGZd95RfadV6Sxh8uZPlM2J+eA7w8AQHu6ZLHt4osv1ty5c3XfffepvLxcWVlZmjZtms4555ymMWeeeaaCwaDuu+8+VVdXKz8/XzfddNMB/yYNAAAAQPww/gSZY06WnTJdWrpI7mvPSMu/bHvwkk/kLvlEW+c/JPf0b8scPKFTswIAeoYuWWwLBAK68MILdeGFF7Y7xhijWbNmadasWZ0XDAAAAEArwWBQc+bMUUpKiq666qp210o+EMZxpLGHyzP2cNn1q2T/85zsJ+9K4XCrsQ2rvpbuukVm1iVypp0Z9SwAgJ6tS67ZBgAAAKDrCIVC+sc//qE777xTobY2NYgyM2iYnEuukXP7fTKnnC0lJbc5zj5xv9y598m6bodnAgD0HF2ysw0AAABA1+H1evWDH/xAKSkp8no770cQk9lL5qwLZE+dJfv+G7KvPycVFbYYY19/Tra0WM4lP5Hx+TstGwCg+6LYBgAAAKBD+f1+3XzzzcrNzdWWLVuiskHCvjCJAZnjZ8gee4r06ny5Tz/UcsCi9+WWl8m56iaZ5HZ2NwUAYC8xjRQAAABAj2Acj5xTv6Ws626VPLv0HRQsk/u7G2SLt8YmHACg26DYBgAAAKBDWWvV0NCghoaGTu9qa0vycafIufpXUiCp5YnCjXJ/91PZ9atikgsA0D1QbAMAAADQoWprazV48GD5/X7V1tbGOo4kyRk1Ts5Pb5cyerU8UV4q946fyy79LDbBAABdHsU2AAAAAD2SGZAn58Y7pH6DWp6or5X711vlLnwjNsEAAF0axTYAAAAAHSoQCOirr75SaWmpAoFArOO0YLKy5dzwO2nkIS1PhMOyD9wt94XH42LqKwCg66DYBgAAAKBDGWOUnp6ujIwMGWNiHacVk5Qi58e/kjn8G63O2WcflX34b7LhcAySAQC6IoptAAAAAHo84/PJfO9amZPOanXOvvea3L/9RrYuPtabAwDEN4ptAAAAADpUMBjUnXfeqV/96lcKBoOxjtMu4zhyzrlQ5tvfl3btwPvyU7l33iRbURqbcACALoNiGwAAAIAOFQqF9Mc//lGzZ89WKBSKdZw9co6fIecHP5N8/pYn1hXI/d0NsoWbYhMMANAlUGwDAAAA0KE8Ho8uuOACXXHFFfJ4PLGOs1fMoUfJ+cmtUnJqyxNFhXLn/FR21dexCQYAiHsU2wAAAAB0qISEBN1+++3629/+poSEhFjH2Wsmf5Scn82RevVpeaKqUu4ffiH7+YexCQYAiGsU2wAAAACgHSZngJwbfy8Nzm95oiEo9//9Tu5bL8YmGAAgblFsAwAAAIDdMOmZcq77jTRmYssT1pV99B658x6Udd3YhAMAxB2KbQAAAAA6VE1NjQYNGiSfz6eamppYx9kvJjEg58qbZKZMa3XOvjJP9v4/yTY0xCAZACDeUGwDAAAA0OFCoVCX2Il0d4zXK/Pdq2RO/3arc/bjd+Te/SvZmqoYJAMAxBOKbQAAAAA6VGJiohYtWqSNGzcqMTEx1nEOiDFGzhnflrngh5Kzy49Ty7+Ue8eNsiXFsQkHAIgLFNsAAAAAdCjHcZSbm6v+/fvL2bVA1UU5U6bJ+eEvpYRdioeb1sm9/XrZjWtjkgsAEHvd4zsdAAAAAHQyM2ainOtvl9IyWp4o2y73jp/Jfr0kJrkAALFFsQ0AAABAhwoGg/r73/+u3//+9woGg7GOE1Vm8DA5P7tDyunf8kRtjdy7fiX3o3diEwwAEDMU2wAAAAB0qFAopNtuu00//elPu/wmCW0x2TlybpgjDTuo5YlwSPa+P8h9ZZ6stbEJBwDodN5YBwAAAADQvXk8Hs2aNUuBQEAejyfWcTqESUmT85Nb5d7/R+mzD1qcs/MelEqKpPMulXG65+sHAOxEZxsAAACADpWQkKC77rpLDzzwgBISEmIdp8MYf4Kcy34qc8Lprc7Zt16S+//myAbrY5AMANCZKLYBAAAAQJQYxyNz7vdkvnVR65NffCj3j7+Urazo/GAAgE5DsQ0AAAAAosgYI2f6TJlLr5O8u6zcs+pruXNukC0qjE04AECHo9gGAAAAoEPV1NTooIMOUkZGhmpqamIdp9M4Rxwj5+rZUiC55Ymtm+Tefr3s2pWxCQYA6FAU2wAAAAB0uIqKCpWXl8c6RqczIw+J7FSa1bvlicpyhe+4UbWfLIhNMABAh2E3UgAAAAAdKjExUQsWLFCfPn2UmJgY6zidzvQfJOdnv5f759nSxrU7TwTrVfzra6X+g6Ws3jJZvaWsbCkrW6bxszIy2cEUALoYim0AAAAAOpTjOBo6dKhyc3O1ZcsWWWtjHanTmcxecq6/Xe4/fid9tXjnCTcsbVgtbVit5u9K02PHkTJ67SzA9erdshiX1VsKJMsY04mvBgCwOxTbAAAAAKATmKRkOT+6WfbBv8h++PbePcl1pZIiqaSo7WKcJCUGdumI693ycWYvGa8vei8EALBbFNsAAAAAdKiGhgb9+9//Vnp6uk4//XR5d92hswcxXp908TVSdo7sy/OkUMOBX7SuVtq8Xtq8vu2CnDFSemY7xbjGj9S0A88BAJBEsQ0AAABAB2toaNBNN90kSTr55JN7dLFNkowxMmd8Rzp+hrKqy7R95XLZ7UVSaZFsSbG0PdLJFpVCnCRZK5WVSGUlsquX7zzcfIzfr61DR8o9/BvS4cfIBJKic28A6IF69nc5AAAAAB3OcRzNmDFDiYmJchwn1nHihklNV+KIg+TkDG61jp21Vqosb5xCWiy7Yypp49cqKZLKS6MXJhhU8Osvpa+/lJ74P5kjjpH5xknSkHzWgwOAfUSxDQAAAECHSkxM1L333tujN0jYV8YYKS0j8jFkuNoqd9mGBqlse6QIt6MbrqRItrRZd1x93b7fvL5O9r3XZN97TRqQJ3PMSTJHTpVJSj7AVwUAPQPFNgAAAADogozPJ2XnSNk5bRfjrJVqqncW4XZ0xDV1yBVJpSWSddu/ycY1so/+Q/apf8kcPiXS7TZ0JN1uALAbFNsAAAAAoBsyxkjJKZGPgXltF+TCYam8RNqwVgmL3lPtR+9GdkDdVbBeduEbsgvfkPoPjnS7TTpWJimlw18HAHQ1FNsAAAAAdKja2lpNmTJFjuPo3XffVSAQiHUkNDIeT2Rn0l591PuUM7X5q6VyF7wemUK6fVvbT9q0Tvaxe2XnPSAzcYrMMSdJww6i2w0AGlFsAwAAANChrLUqLCyMdQzsBZPRS85ps2RPOUf6arHcd1+VFn8khcOtBweDsh+8KfvBm1K/QY3dbsfJJNPtBqBno9gGAAAAoEMlJCTotddeU3Z2thISEmIdB3vBOI508AR5Dp4gW14q+/4bkW63onaKppvXyz7+T9l5D8pMnBzpdssfRbcbgB6JYhsAAACADuXxeDRmzBh2I+2iTHqmzCnnyJ50lvT1Etl3X5X94sO2u90agrIfviX74VtS7kCZY6ZHut1S0jo/OADECMU2AAAAAMAeGceRRo+XGT1etqJU9v03Zd99tf1uty0bZOfeLzvvIZmJR0e63YYfTLcbgG6PYhsAAACADtXQ0KD58+crIyNDxx9/vLxefgzp6kxapszJZ8tOnykt/1L2vddkP/tACodaDw41yH70juxH70g5/WW+cZLMUcfLpNLtBqB74rscAAAAgA7V0NCga665RpJUUFBAsa0bMY4jjRonM2qcbGX5zm63bZvbfkLhJtkn/092/kMyE46KdLuNPIRuNwDdCt/lAAAAAHQox3F0wgknKCEhQY7jxDoOOohJTZc5aabs9G9KK5ZG1nb77H0p1Fa3W0j2k/dkP3lP6tMvsrbbUcfLpGV0dmwAiDqKbQAAAAA6VGJioh5++GE2SOghjDGRbrWRh8hWVkQ2THj3ValwY9tP2LZZ9qkHZOc/IjNhkszUk2X79u3c0AAQRRTbAAAAAAAdwqSmyUw7U/bEM6SVy2Tfe1X204VSqKH14HBI9tMFsp8uUOHj98o94Qxp0nEyPl/nBweAA0CxDQAAAADQoYwx0oiDZUYcLHvepbIfNHa7bdnQ5vjQpvXSQ3+Vnn1U5sTTZaaeIhNI6uTUALB/WDABAAAAQIeqra3V5MmTNXz4cNXU1MQ6DmLMJKfKOfEMObP/KueG38kcdZzk87c9uLxEdt6Dcm+4RO7TD8qWl3ZuWADYD3S2AQAAAOhQ1lqtWbMm1jEQZ4wxUv5omfzRsudeKvvh27LvvSptWtd6cG217MvzZP/znMzRJ8ic9E2ZPv06PzQA7AWKbQAAAAA6VEJCgp555hn17t1bCQkJsY6DOGSSU2ROmCF7/GnSV1/I9+YLql/8SeuBoQbZd1+Rfe81mUOPkjnlbJnB+Z0fGAB2g2IbAAAAgA7l8Xh0xBFHsBsp9sgYI3Pwoepz4mna/P47Cr88T/r8A2nXvzPWlV20UHbRQmnUODmnnCMdNDbSLQcAMUaxDQAAAAAQd0zeCHku/5ls4SbZ1+bLfvCmFAq1HvjVYrlfLZYG58s5+Szp0KNkHE/nBwaARhTbAAAAAHSoUCikV155RZmZmZo0aZI8Hgoh2Hsmp7/Md6+SPePbsq8/L/vOy1JdbeuB6wrk3nOH1CdX5qSZMkcdL9PexgsA0IEotgEAAADoUMFgUJdddpkkqaCgQIFAIMaJ0BWZjF4y51woe+o5su+8Ivv6c1JFWeuB27bIPvx32ecekznhDJmpJ8skJXd6XgA9F8U2AAAAAB3KGKOjjjpKfr+fNbVwwExSiswp58ieeIbs+2/Kvvq0VFTYemB5qezTD8q+/KTMMSfLnHiGTEZW5wcG0ON02WJbSUmJHnnkEX3xxRcKBoPKzc3V5ZdfrqFDh0qKbC/+5JNP6o033lBVVZWGDx+uSy65RAMHDoxxcgAAAKBnCQQCmjdvHhskIKqMzy8z9WTZb0yTXfSB7CvzpPWrWg+srZF99WnZN56LTC096SyZvv06PzCAHqNLFtuqqqr0y1/+UgcffLB+/vOfKy0tTVu3blVSUlLTmGeffVYvvviirrjiCuXm5urpp5/Wbbfdprvuuou2dQAAAADoJozjkTl8iuxhkyObJbwyT/pqceuBoZDse6/JLviPdOhRck4+W2bI8M4PDKDb65LFtmeffVa9evXSFVdc0XSsT58+TY+ttXrppZc0c+ZMHXnkkZKkK6+8UpdeeqkWLFigadOmtbpmQ0ODGhoamr42xjQV5Xp6q/uO1x8P7wNZ4jdH8wxkaSlessRLjuYZyNJSvGSJlxzNM5ClpXjJEi85mmcgS0tkid8czTOQpaX9zWKMkQ6eIOfgCbJrV8p9ZZ7sovelXTsprZUWvS930fsyo8bJnHy2zOjxre7XHd6TjkCW+M3RPANZYsvYLtjDfc0112jcuHEqKSnRsmXLlJWVpenTp+vEE0+UJG3dulU//OEPNWfOHOXl5TU974477lBSUpKuuuqqVtd84okn9NRTTzV9nZeXpzlz5nT8iwEAAAC6udraWh111FGSpA8++ICZJug0DZvWq/Lph1X9+gtSqKHdcb5hByntnO8qMPkEGXbLBXCAumRn27Zt2/Sf//xHp512mmbOnKmCggL961//ks/n09SpU1VWViZJSk9Pb/G89PR0FRcXt3nNmTNnasaMGU1f76i8FhUVKRQKdcwL6SKMMcrJyVFhYWHM19cgS/zmIEv8Z4mXHGSJ/yzxkoMs8Z8lXnKQJf6z1NTUaPHiyLS+LVu2xLTYFi/vCVk6KYvjk865WJ4Tvyn39edk33lZqq1pNaxh1dfaPufnUnaunJNmykw+QY4/oXu+J2TptjnI0vG8Xq+ys7P3PK4TskSd67oaNmyYvvOd70iKdKFt2LBBr732mqZOndo0btdWxd394fp8Pvl8vjbPdZe/FAfKWhs37wVZ4jeHRJb2xEuWeMkhkaU98ZIlXnJIZGlPvGSJlxwSWdoT6yx+v1+PPfaYevXqJb/fHxfvS6zfk+bI0raoZknPlHP2BbKnnCP7ziuybzwnlZe2Hle0Re4jf5eee1T2xDPknntR931PDhBZ4jeHRJZY65LFtszMTA0YMKDFsQEDBuijjz6SJGVkZEiSysrKlJmZ2TSmoqKiVbcbAAAAgI7l8Xg0depUdiNFzJmkZJlTzpY98XTZD96SffVpaduW1gMryuQ+/ZA2vzxPOni8lDdCJm+kNGiYTEJCp+cG0LV0yWLbyJEjtXnz5hbHNm/e3NTK16dPH2VkZGjJkiVNa7aFQiEtW7ZM559/fqfnBQAAAADED+PzyxxzkuyUE6XPP5T78jxpXUGrcba2Wvp0ofTpQllJchyp/+BI4W3oCJkhI6TcATKO0+mvAUD86pLFttNOO02//OUv9fTTT+voo49WQUGB3njjDX3/+9+XFJk+euqpp2r+/PnKzc1VTk6O5s+fr4SEBE2ZMiXG6QEAAICeJRQK6Z133lFWVpbGjh0rDwvQI04YxyNNnCzn0KOlr5fIfWWetOyL9p/gutKGNbIb1kjvvhIpwCUGpCHDZfJGyOSNiHTBZWR10isAEI+6ZLEtPz9f1113nR599FHNmzdPffr00QUXXKBvfOMbTWPOPPNMBYNB3XfffaqurlZ+fr5uuukmdj4CAAAAOlkwGNR3v/tdSVJBQQH/T464Y4yRRo2TZ9Q42XUFsq88Lbvofcm6e35yXa309RLZr5eoaYJ0Vu/GqaeNBbjB+TIJiR35EgDEkS5ZbJOkiRMnauLEie2eN8Zo1qxZmjVrViemAgAAALArY4zGjRsnn8/XahMzIN6Ywfkyl/1UKilS2qY1KvviE9nVK6RN6/au+CZJJcVSSbHsovcjBTjjSP0H7ex8GzqycfopXZ5Ad9Rli20AAAAAuoZAIKCXX36ZDRLQpZhefZQyZpwqxx4Z2U2xvk5aVyC7ZqXsmuXSmhWRotresK60ca3sxrXSe69FCnAJAWlIfmP323Apb6RMZq8OfEUAOgvFNgAAAAAA9sAkJEojxsiMGNN0zJaVSGtXyK5eIbtmhbR2ZWRa6d6or5WWfym7/Mud008zejVtvGCGNk4/TWTaNdDVUGwDAAAAAGA/mIwsafwkmfGTJEnWdaXCjZHC2+oVsmtXSBvXRjZW2Btl26XPPpD97IOd00/7DZTJG6Gq8YfLpvWS7TdIJiGho14SgCig2AYAAACgQ9XW1uq8886T3+/Xww8/rMREFopH92QcR+o3SKbfIGnyiZIkW18vrV/VOPV0ZaQQt33b3l3QutKmdbKb1ql0wX923ETq209mYJ40YEjk88A8KT2LNRGBOEGxDQAAAECHstbq008/bXoM9CQmIUEaPlpm+OimY7aiNFJ4W70iUoRbu1Kqrdm7C9rG7rnCjdIn7+2cgpqSJg3MkxkwRBo4VGbgEClngIzXF+VXBGBPKLYBAAAA6FB+v1/333+/srKy5Pf7Yx0HiDmTlimNO0Jm3BGSGqefbt3ctPFCZPfTtVI4vPcXraqQvlos+9XiyDUlyeOVcgc2db9FCnF5Milp0X5JAJqh2AYAAACgQ3m9Xp1yyinsRgq0wziOlDtAJneAdPQJkiQbrJfWr45MO12zQp4t6xXatF7al38/4ZC0cY3sxjXSB2q5EcPAvMapqHmRLrg+uTKOJ9ovDeiRKLYBAAAAABBnjD9Byh8lkz9Kxhjl5uZq89o1shvXRopnG3Z+Vv1e7oC6Q9l2qWy77JeN07slyZ8g9R/crACXJw0YLJOYFO2XBnR7FNsAAAAAdKhwOKyPP/5YvXr1Un5+vhzHiXUkoEsyCYnS0JEyQ0c2HbOuKxVvjXSwbVgru2F1ZAfUvd2EYYdgfWQK65oVkevuOJ6d0zgFtbEAN2iobE5OVF4P0F1RbAMAAADQoerr63XOOedIkgoKChQIBGKcCOg+jONIfXIj00APPbrpuK2pkjauld2wRtoQ6YjTpnVSqGHfblBUKBUVyn72QVMBbqPXKwVSpORkKTlVSkqRSU6Rkho/Gh+b5NQWY5SUIuNjwwZ0fxTbAAAAAHQoY4xGjBghr5cfP4DOYpJSpBFjZEaMaTpmw2GpcFPj9NM1TZ9VUbZvFw+FpMqyyMeOa7cxrM3V5fwJOwtyySlSUqpMcnLjsdSmc6bFmBQpKZk15dBl8N0OAAAAQIcKBAJ6++232SABiDHj8Uj9B8n0HyQdObXpuK0o3bkG3PrGIlzhRsl1ox8iWB/5KNu+8/5tDGvzvxKB5J3Ft+RIN13ZwDy5KWlSdq7Ut7+UkRXp9gNiiGIbAAAAAAA9mEnLlA7OlDl4QtMx2xCUNm9oWgPOblgjbVwj1VTHLmhtdeRDWyMZJVV+urDlGL+/sfDWT6ZvP6lPP5k+/aScflJqhowxnR4bPQ/FNgAAAAAA0ILx+aXBw2QGD2s6Zq2VKduu3n6fitavla2qlGqqpOodn6sia8VVN37seLyvu6UeiGAwsjbdpnVN3XFNXXKJgUjxrW8/qakQlyvl9I+sLwdECcU2AAAAAB2qtrZWF110kRISEnTPPfcoMTEx1pEA7AdjjExWtvy5uXJSMvZ6SrgNhSIdadWVTUU427wg11iwszXNxzQ+3tcNHXanrlZav0p2/aqd2XY8SE6NbDLRvBC343MgKXoZ0CNQbAMAAADQoay1eu+995oeA+hZjNcrpaZHPnYc28vn2mB9yy65mkrZ6sZCXFWFEivLVLtutbRts1Rft/8hqyulNZWya1bsvPeOB2kZjcW3yLpwpk8/qW+ulN1PJiFh/++JbotiGwAAAIAO5ff79de//lUZGRny+/2xjgOgCzH+hMgOppm9dh7b8dkY9W7ceMV1Xam8RNq6RXbbZmnrJtmtWyJFuG1bDqxDrqJMqiiTLVgmaZfNGzJ7y/Ttp5Khw+WOGCuNHs+6cKDYBgAAAKBjeb1enXXWWexGCqDDGGOkjF5SRi+ZkWNanLOuK5UWS1s3NxbiNstubSzCFRdK4fD+37i0WLa0WNVfL5FemicNGS5nxnnS2MMouvVgFNsAAAAAAEC3ZRxH6tVH6tVHZvT4FudsOCxt39p2R9z2bdK+/nJg7Uq5f71VGjRMzunnSuOOpOjWA1FsAwAAANChwuGwli5dqo0bNyo3N1eO48Q6EgBIkozHI/Vp3AhBE1ucsw0Nkc63tjriSot3f+H1q+T+7bfSwLxIp9v4IyNFP/QIFNsAAAAAdKj6+nqdeuqpkqSCggIFAoEYJwKAPTM+n5Q7UMod2GpDB1tfLxVtlrZukQo3yvn4HYU2b2h9kQ1r5P6/26UBQyJFtwmTKLr1ABTbAAAAAHQoY4wGDBggj8cT6ygAEBUmIUEakCcNyJMxRjkXX6XNz86V+8IT0tZNrZ+wca3cf/xO6j9Y5rRzZSYeTdGtG6PYBgAAAKBDBQIBffzxx2yQAKDbMh6vnKOOl444Rvbj92RfnCsVtlF027RO9t47ZHMHysw4V+awyTIOv4jobiijAgAAAAAARIFxPHImHStn9l9lvndtZBpqW7ZskP3nnXJv+aHcD9+WdQ9gR1TEHYptAAAAAAAAUWQcj5wjp8r51V9kvv9Tqd+gtgcWbpS9/49yb75K7gdvRXZHRZdHsQ0AAABAh6qrq9NFF12kb37zm6qrq4t1HADoNMZx5Bw+Rc4tf5bzgxuk/oPbHrh1k+z//UnuzVfKff8Nim5dHMU2AAAAAB3KdV29+uqrevbZZ+W6bqzjAECnM44jM3GynJvvlnP5zyKbK7Rl22bZf90t95eXy134umwo1LlBERVskAAAAACgQ/l8Pv3+979Xenq6fD5frOMAQMwYx5EOPVrOhKOkxR/Jff5xaf3q1gOLCmUf+LPsC3NlTv2WzFHHyXj572dXQbENAAAAQIfy+Xw6//zz2Y0UABoZY6Txk+SMO1Ja8kmk6LauoPXA4q2yD/1V9sUnZE49R+boEyi6dQEU2wAAAAAAAGLAGCONO0LO2MOlLz+NFN3Wrmw9cPs22Yf/LvvikzKnnCMz+UQZOoXjFsU2AAAAAB3KdV0VFBSopKREGRkZkR8uAQBNjDHS2MPlHHKY9N/PIkW31ctbDywpkv33/5N96UmZU86WmTJNxufv/MDYLYptAAAAADpUXV2djjvuOElSQUGBAoFAjBMBQHwyxkhjJso5+FBp2Rdyn39MWvV164GlxbKP3hMpup18jsw3psn4Ezo/MNpEsQ0AAABAh8vKypLjOLGOAQBdgjFGOniCnNHjpa8WRzrdCpa1HlhWIvv4vbIvPyVz8kw5U0/p9KxojWIbAAAAgA6VlJSkpUuXskECAOwjY4w0erycUeOk5V9Gim4rlrYeWF4iO/d+hV+ep8pZF8pOmCzR6RYzFNsAAAAAAADimDFGOmisPAeNlV2+NDK9dPmXrQdWlKnsvrukjIdkzvhOZPdSj6fT8/Z09HEDAAAAAAB0EWbkGHmu+42c62+XRo1re1BZiexDf5U7+0eyiz+ho7iTUWwDAAAA0KHq6up05ZVX6vzzz1ddXV2s4wBAt2BGHCzPT26Vc8PvpNET2h60ZYPcv94q986fy7a1uyk6BMU2AAAAAB3KdV3Nnz9fjz76qFzXjXUcAOhWTP5oea6ZLednd8iMGt/2oBX/lXv79Qr/43eyWzd3ar6eiDXbAAAAAHQon8+n2bNnKy0tTT6fL9ZxAKBbMsMOknPtrcrcsk5F9/5B2rCm9aBF78v94iOZY06SmXGeTFpGp+fsCSi2AQAAAOhQPp9Pl156KbuRAkAnSDx0kjy/vEvuR2/LPvNvafu2lgPCYdm3XpJ9/y2Zk2bKTDtTJjEQm7DdFNNIAQAAAAAAuhHjOHImHSfn1r/LfOsiKSml9aD6WtnnHpV702Vy33lFNhzu/KDdFMU2AAAAAB3KdV1t2LBBa9euZc02AOhExueXM32mnN/eK3PSWZK3jan8FWWyj/xd7q+ukv3sA7qPo4BiGwAAAIAOVVdXpyOPPFJ5eXnsRgoAMWCSU+Scc6Gc2/4hc9TxkjGtBxVukvv/bpc75wbZgmWdH7IbodgGAAAAoMMFAgElJSXFOgYA9GimV7aci6+Wc/Nd0piJbQ9a9bXcOT9T+G+/ld2ysVPzdRcU2wAAAAB0qKSkJK1atUrV1dUU3AAgDpgBefL8+BY5P7lVGpzf9qAvPpT7q6vkPvw32bKSzg3YxVFsAwAAAAAA6IHMqHFyfn6nzKXXSb37th7gurLvvhrZROHZf8vW1XR+yC6IYhsAAAAAAEAPZRxHzhHHRHYuPe9SKSW19aBgvewLc+X+/DK5b70oGwp1ftAuhGIbAAAAgA5VX1+v6667Tpdeeqnq6+tjHQcA0Abj9ck54XQ5v7lX5tRvSX5/60GV5bKP3iP3litlP13AzqXtoNgGAAAAoEOFw2E9+uijuu+++xQOh2MdBwCwGyYpWc7M/5Vz2z0yU6ZJpo3S0bYtcu+5Q+7t18suX9r5IeOcN9YBAAAAAHRvXq9XN9xwg1JTU+X18iMIAHQFJrOXzAU/lD3xTLnzH5IWf9x60JoVcu/8uTT2cDlnXSDTf1DnB41DfKcDAAAA0KH8fr9+/OMfKzc3V1u2bGHaEQB0Iab/IHmu+oXsiqVyn3pAWrOi9aAln8j9cpHM0cfLnHm+TGavTs8ZT5hGCgAAAAAAgN0yI8bIufH3cn5wg9Qnt/UA68oufF3uLy6T+/RDsjXVnR8yTtDZBgAAAKBDWWtVUlIir9dLVxsAdGHGGGniZDnjjpR97zXZ5x+TKstbDgoGZV9+SuH3XlXld74vO2Gy1MOWEKCzDQAAAECHqq2t1SGHHKI+ffqotrY21nEAAAfIeL1yjjtVzm/vkZlxnpSQ2HpQVaXK7v2Dwr+8XPaLjzo/ZAxRbAMAAAAAAMA+M4lJcs78jpzf3CMz9WTJaaPMVLxVtrqy88PFEMU2AAAAAB0qKSlJmzdvlrVWSUlJsY4DAIgyk54p53+ukDP7r9KESS1P9h8sc9RxsQkWI11+0uz8+fP12GOP6dRTT9WFF14oKbImxJNPPqk33nhDVVVVGj58uC655BINHDgwtmEBAAAAAAC6KZMzQJ4rfi5b8JXceQ9IBV/JOftCGccT62idqkt3thUUFOj111/X4MGDWxx/9tln9eKLL+riiy/W7bffroyMDN12222sDwEAAAAAANDBTP4oeW6Yo+zb/yFzyMRYx+l0XbbYVldXp7/85S+67LLLlJyc3HTcWquXXnpJM2fO1JFHHqlBgwbpyiuvVH19vRYsWBDDxAAAAAAAAD2DMUaJYw+L7GDaw3TZaaT33XefJkyYoLFjx+rpp59uOr5t2zaVlZVp3LhxTcd8Pp9Gjx6t5cuXa9q0aW1er6GhQQ0NDU1fG2MUCASaHvdkO15/PLwPZInfHM0zkKWleMkSLzmaZyBLS/GSJV5yNM9AlpbiJUu85GiegSwtkSV+czTPQJaW4iVLvORonoEsLcVLlnjJ0TwDWWKrSxbbFi5cqDVr1uj2229vda6srEySlJ6e3uJ4enq6iouL273m/Pnz9dRTTzV9nZeXpzlz5ig7Ozs6obuBnJycWEdoQpbW4iWHRJb2xEuWeMkhkaU98ZIlXnJIZGlPvGSJlxwSWdpDltbiJYdElvbES5Z4ySGRpT3xkiVeckhkibUuV2wrLi7WAw88oJtuukl+v7/dcbtWTq21u73uzJkzNWPGjFbPLyoqUigUOoDEXZ8xRjk5OSosLNzj+0iWnpuDLPGfJV5ykCX+s8RLDrLEf5Z4yUEWsnTFHGSJ/yzxkoMs8Z8lXnKQpeN5vd69asrqcsW21atXq7y8XD/72c+ajrmuq6+++kqvvPKK7rrrLkmRDrfMzMymMRUVFa263Zrz+Xzy+XxtnusufykOlLU2bt4LssRvDoks7YmXLPGSQyJLe+IlS7zkkMjSnnjJEi85JLK0hyzxm0MiS3viJUu85JDI0p54yRIvOSSyxFqXK7YdcsghuvPOO1sc+3//7/+pX79+OvPMM9W3b19lZGRoyZIlysvLkySFQiEtW7ZM559/fiwiAwAAAAAAoIfocsW2QCCgQYMGtTiWkJCg1NTUpuOnnnqq5s+fr9zcXOXk5Gj+/PlKSEjQlClTYhEZAAAAAAAAPUSXK7btjTPPPFPBYFD33XefqqurlZ+fr5tuuqlpd1EAAAAAAACgI3SLYtuvfvWrFl8bYzRr1izNmjUrNoEAAAAAAADQIzmxDgAAAAAAAAB0FxTbAAAAAAAAgCih2AYAAAAAAABECcU2AAAAAAAAIEootgEAAAAAAABRQrENAAAAAAAAiBJvrAPEO6+Xt2iHeHovyNJavOSQyNKeeMkSLzkksrQnXrLESw6JLO2JlyzxkkMiS3vI0lq85JDI0p54yRIvOSSytCdessRLDoksHWVvX4ux1toOzgIAAAAAAAD0CEwjxR7V1tbqhhtuUG1tbayjkCWOc5Al/rPESw6yxH+WeMlBlvjPEi85yEKWrpiDLPGfJV5ykCX+s8RLDrLED4pt2CNrrdasWaN4aIIkS/zmIEv8Z4mXHGSJ/yzxkoMs8Z8lXnKQhSxdMQdZ4j9LvOQgS/xniZccZIkfFNsAAAAAAACAKKHYBgAAAAAAAEQJxTbskc/n0znnnCOfzxfrKGSJ4xxkif8s8ZKDLPGfJV5ykCX+s8RLDrKQpSvmIEv8Z4mXHGSJ/yzxkoMs8YPdSAEAAAAAAIAoobMNAAAAAAAAiBKKbQAAAAAAAECUUGwDAAAAAAAAooRiGwAAAAAAABAlFNsAAAAAAACAKKHYBgAAAAAAAEQJxTYAAAAAAAAgSii2AQAAAAAAAFFCsQ0AAAAAAACIEoptAAAAAAAAQJRQbAMAAAAAAACihGIbAAAAAAAAECUU2wAAAAAAAIAoodgGAAAAAAAARAnFNgAAAAAAACBKKLYBAAAAAAAAUUKxDQAAAAAAAIgSim0AAAAAAABAlFBsAwAAAAAAAKKEYhsAAAAAAAAQJRTbAAAAAAAAgCih2AYAAAAAAABECcU2AAAAAAAAIEootgEAAAAAAABRQrENAAAAAAAAiBKKbQAAAAAAAECUUGwDAAAAAAAAooRiGwAAAAAAABAlFNsAAAAAAACAKKHYBgAAAAAAAEQJxTYAAAAAAAAgSii2AQAAAAAAAFFCsQ0AAAAAAACIEoptAAAAAAAAQJRQbAMAAAAAAACihGIbAAAAAAAAECUU2wAAAAAAAIAoodgGAAAAAAAARAnFNgAAAAAAACBKKLYBAAAAAAAAUUKxDQAAAAAAAIgSim0AAAAAAABAlFBsAwAAAAAAAKKEYhsAAAAAAAAQJRTbAAAAAAAAgCih2AYAAAAAAABECcU2AAAAAAAAIEootgEAAAAAAABRQrENAAAAAAAAiBKKbQAAAAAAAECUeGMdIN6VlpYqFArFOkbMZWdnq6ioKNYxJJElnnNIZGlPvGSJlxwSWdoTL1niJYdElvbES5Z4ySGRpT1kid8cElnaEy9Z4iWHRJb2xEuWeMkhkaUjeb1eZWZm7nlcJ2Tp0kKhkBoaGmIdI6aMMZIi74W1lixxliVecpAl/rPESw6yxH+WeMlBlvjPEi85yEKWrpiDLPGfJV5ykCX+s8RLDrLEj5gX25YtW6bnnntOa9asUWlpqa677jodccQRe3zOgw8+qI0bNyozM1NnnHGGpk+f3mLMhx9+qLlz52rr1q3q27evvv3tb+/xugAAAAAAAMCBiPmabfX19RoyZIguvvjivRq/bds23X777Ro1apTmzJmjmTNn6l//+pc+/PDDpjErVqzQXXfdpWOOOUa///3vdcwxx+hPf/qTVq5c2VEvAwAAAAAAAIh9Z9uECRM0YcKEvR7/2muvqXfv3rrwwgslSQMGDNCqVav0/PPPa9KkSZKkF198UWPHjtXMmTMlSTNnztSyZcv04osv6uqrr27zug0NDS2mixpjFAgEmh73ZDtefzy8D2SJ3xzNM5ClpXjJEi85mmcgS0vxkiVecjTPQJaW4iVLvORonoEsLZElfnM0z0CWluIlS7zkaJ6BLC3FS5Z4ydE8A1liK+bFtn21cuVKjR07tsWx8ePH66233lIoFJLX69WKFSt02mmntRgzbtw4vfTSS+1ed/78+Xrqqaeavs7Ly9OcOXOUnZ0d3RfQheXk5MQ6QhOytBYvOSSytCdessRLDoks7YmXLPGSQyJLe+IlS7zkkMjSnlhnqa6uVkpKiiSpqqpKycnJMc0jxf49aY4sbYuXLPGSQyJLe+IlS7zkkMgSa12u2FZWVqb09PQWx9LT0xUOh1VZWanMzEyVlZUpIyOjxZiMjAyVlZW1e92ZM2dqxowZTV/vqLwWFRX1+N1IjTHKyclRYWFhzBc1JEv85iBL/GeJlxxkif8s8ZKDLPGfJV5ykCX+swSDQV177bVKSUnR9u3bVVFREbMs8fKekCX+s8RLDrLEf5Z4yUGWjuf1eveqKavLFduk1i2IO/7QdteaaK3d7Xmfzyefz9fucxF5H+LlvSBL/OaQyNKeeMkSLzkksrQnXrLESw6JLO2JlyzxkkMiS3tincXn8+naa69Vbm6utmzZEhfvS6zfk+bI0rZ4yRIvOSSytCdessRLDokssRbzDRL2VVsdahUVFfJ4PE2t6W2NKS8vb9URBwAAAAAAAERTlyu2DR8+XEuWLGlxbPHixRo6dKi83kij3ogRI/Tll1+2GLNkyRKNGDGi03ICAAAAiLDWqry8XGVlZT2uuwEA0PPEvNhWV1entWvXau3atZKkbdu2ae3atSouLpYkPfroo/rrX//aNH769OkqLi7Wgw8+qI0bN+rNN9/Um2++qdNPP71pzKmnnqrFixfrmWee0aZNm/TMM8/oyy+/bLVpAgAAAICOV1tbq1GjRikzM1O1tbWxjgMAQIeK+Zptq1at0uzZs5u+fuihhyRJU6dO1ZVXXqnS0tKmwpsk9enTRzfeeKMefPBBvfrqq8rMzNRFF12kSZMmNY0ZOXKkrr76aj3++OOaO3eucnJydPXVV2v48OGd98IAAAAAAADQ48S82HbwwQfriSeeaPf8lVde2erY6NGjNWfOnN1ed9KkSS0KcAAAAABiIxAIaN26dcrNzVVRUVGs4wAA0KFiPo0UAAAAQPdmjJHP55PP55MxJtZxAADoUFEpthUXF+uLL75QZWVlNC4HAAAAAAAAdEn7PI308ccfV11dnS688EJJkV0+58yZo1AopOTkZM2ePVsDBw6Mdk4AAAAAXVQwGNScOXOUkpKiq666Sj6fL9aRAADoMPvc2fbRRx9pwIABTV/PnTtXgwcP1vXXX6/s7GzNmzcvqgEBAAAAdG2hUEj/+Mc/dOeddyoUCsU6DgAAHWqfO9tKSkqUk5MjSaqsrFRBQYFuvPFGjR8/XsFgUA8//HDUQwIAAADourxer37wgx8oJSVFXm/M92gDAKBD7fN3OmutrLWSpOXLl8txHI0ePVqSlJmZqYqKiugmBAAAANCl+f1+3XzzzcrNzdWWLVuafp4AAKA72udppH379tWiRYskSQsXLlR+fr78fr8kqbS0VCkpKdFNCAAAAAAAAHQR+9zZNm3aNN1///169913VV1drcsvv7zp3PLly1us5wYAAAAA1lqFQiE1NDTQ1QYA6Pb2udg2ffp0JScna/ny5crPz9cxxxzTdC4YDGrq1KlRDQgAAACga6utrdXw4cMlSQUFBQoEAjFOBABAx9mv1UknT56syZMntzp+2WWXHXAgAAAAAAAAoKva5zXbzj33XBUUFLR5bvXq1Tr33HMPOBQAAACA7iMQCOirr75SaWkpXW0AgG5vn4ttu+O6bjQvBwAAAKAbMMYoPT1dGRkZMsbEOg4AAB0qqsW21atXKzk5OZqXBAAAAAAAALqMvVqz7aWXXtJLL73U9PXvf/97+Xy+FmOCwaDKy8s1adKk6CYEAAAA0KUFg0H95S9/UWpqqi666KJWP0sAANCd7FWxLS0tTQMGDJAkFRUVqW/fvkpKSmoxxufzadCgQTr11FOjnxIAAABAlxUKhfTHP/5RkvS///u/FNsAAN3aXhXbpkyZoilTpkiSZs+ere9973vq379/hwYDAAAA0D14PB5dcMEFSk5OlsfjiXUcAAA61F4V25q75ZZbOiIHAAAAgG4qISFBt99+u3Jzc7VlyxZZa2MdCQCADrPPxTZJstZq1apVKioqUjAYbHV+6tSpBxwMAAAAAAAA6Gr2udi2efNm3XHHHdqyZUu7Yyi2AQAAAAAAoCfa52Lb/fffr4aGBl1zzTUaNGgQi5sCAAAA2K2amhqNGjVKkvT1118rEAjEOBEAAB1nn4ttBQUFuuyyyzRp0qSOyAMAAACgGwqFQrGOAABAp9jnYltiYqKSkpI6IgsAAACAbigxMVGLFi1S3759Yx0FAIAOt8/FtuOOO04LFizQ+PHjoxbi1Vdf1XPPPaeysjINGDBAF154YVOb+a7+9re/6Z133ml1fMCAAfrjH/8oSXr77bf197//vdWYRx55RH6/P2q5AQAAAOyZ4zjKzc1lN1IAQI+wV8W2jz76qOnxoEGDtGDBAs2ZM0cTJ05Uampqq/FHHnnkXgd4//339cADD+h73/ueRo4cqddff12//e1v9ac//Um9e/duNf6iiy7S+eef3/R1OBzW9ddf32paayAQ0N13393iGIU2AAAAAAAAdKS9Krbt6BhrrqioSJ999lmb4+fOnbvXAV544QUdf/zxOuGEEyRJF154oRYvXqzXXntN3/nOd1qNT0pKajGN9eOPP1Z1dbWOO+64FuOMMcrIyNjrHAAAAAA6RjAY1P3336+0tDTNmjWLTdYAAN3aXhXbbrnllg65eSgU0urVq/XNb36zxfGxY8dq+fLle3WNN998U4cccoiys7NbHK+rq9MVV1wh13U1ZMgQnXvuucrLy2v3Og0NDWpoaGj62hjTtEuSMWYvX1H3tOP1x8P7QJb4zdE8A1laipcs8ZKjeQaytBQvWeIlR/MMZGkpXrLES47mGcjSUrxkCYfDuu222yRJZ599dkxnnMTLe9I8A1laipcs8ZKjeQaytBQvWeIlR/MMZIktY2O4YEJJSYl+8IMf6NZbb9XIkSObjj/99NN65513Wk0D3VVpaakuv/xy/ehHP9LRRx/ddHzFihUqLCzUoEGDVFtbq5deekmff/65fv/73ys3N7fNaz3xxBN66qmnmr7Oy8vTnDlzDvAVAgAAAKivr9dll10mSbrnnnuUkJAQ40QAAHScfd4goSO0VeXcm8rn22+/reTkZB1xxBEtjo8YMUIjRoxo+nrkyJG64YYb9PLLL+viiy9u81ozZ87UjBkzWt2/qKiox29TboxRTk6OCgsLY76YLVniNwdZ4j9LvOQgS/xniZccZIn/LPGSgyxdI8vvfve7uMgST+8JWeI7S7zkIEv8Z4mXHGTpeF6vt9XMyjbH7euFZ8+e3e45x3GUlJSkYcOG6fjjj1daWtpur5WWlibHcVRWVtbieHl5udLT03f7XGut3nrrLX3jG9+Q17v7l+E4joYNG6bCwsJ2x/h8vnbXjugufykOlLU2bt4LssRvDoks7YmXLPGSQyJLe+IlS7zkkMjSnnjJEi85JLK0hyzxm0MiS3viJUu85JDI0p54yRIvOSSyxJqzr0+w1mrz5s1atmyZioqKFAwGtW3bNi1btkybNm3S1q1b9eSTT+raa6/Vli1bdnstr9eroUOHasmSJS2OL1mypMW00rYsW7ZMhYWFOv744/cq87p169gwAQAAAAAAAB1qnzvbzjrrLN1zzz267bbbNHz48KbjK1as0F133aVvf/vb6t+/v2699VY9/vjjuuaaa3Z7vRkzZugvf/mLhg4dqhEjRuj1119XcXGxpk2bJkl69NFHVVJSoquuuqrF8958800NHz5cgwYNanXNJ598UsOHD1dubm7Tmm1r167VJZdcsq8vFwAAAMABqqmp0cSJE2WM0aJFi5o2IgMAoDva52LbY489prPPPrtFoU2KrJN2zjnn6LHHHtMdd9yhM844Q3Pnzt3j9Y4++mhVVlZq3rx5Ki0t1cCBA3XjjTc2zYEtLS1VcXFxi+fU1NToo48+0oUXXtjmNaurq3XvvfeqrKxMSUlJysvL0+zZs5Wfn7+vLxcAAABAFFRUVMQ6AgAAnWKfi23r169X79692zzXq1cvbdq0SZLUv39/1dTU7NU1TzrpJJ100kltnrvyyitbHUtKStIjjzzS7vUuvPDCdgtxAAAAADpXYmKiFixYoD59+igxMTHWcQAA6FD7vGZbRkaGPv744zbPffzxx00bG9TU1Cg5OfnA0gEAAADo8hzH0dChQzV8+HA5zj7/CAIAQJeyz51tJ5xwgubOnauamhpNmjRJGRkZKisr0/vvv68PPvhA5513nqTIGm5tracGAAAAAAAAdFf7tUFCXV2dXnzxRS1cuHDnhbxeffOb39TMmTMlSZMnT96rnUIBAAAAdG8NDQ3697//rfT0dJ1++unyevf5xxAAALqM/fou953vfEdnnnmmVq5cqcrKSqWmpio/P18pKSlNYwYPHhy1kAAAAAC6roaGBt10002SpJNPPpliGwCgW9vv73LJyckaP358FKMAAAAA6I4cx9GMGTOUmJjImm0AgG5vr4ptxcXFysjIkNfrVXFx8R7Ht7dbKQAAAICeJzExUffee69yc3O1ZcsWWWtjHQkAgA6zV8W2K6+8Ur/5zW+Un5+vK6+8co/j586de8DBAAAAAAAAgK5mr4ptl19+ufr27dv0GAAAAAAAAEBre1VsO/bYY9t8DAAAAAB7UltbqylTpshxHL377rsKBAKxjgQAQIc5oG2AgsGgqqqqlJ6eLo/HE61MAAAAALoRa60KCwtjHQMAgE6xX8W2pUuX6rHHHtOqVaskSb/97W81dOhQ3XfffTrkkEN05JFHRjUkAAAAgK4rISFBr732mrKzs5WQkBDrOAAAdKh93nd76dKl+s1vfqOGhgadfvrpLXYSSktL09tvvx3NfAAAAAC6OI/HozFjxmj8+PHMiAEAdHv7XGybO3euJkyYoDvuuEPnnXdei3ODBw/W2rVro5UNAAAAAAAA6FL2udi2du1anXjiiZIkY0yLc2lpaaqoqIhOMgAAAADdQsP/b+/e46qq8/2PvzcCclFAwwQvpYyCiqF20uxytHTMpiwvo3bSJpHUHmnnoZ7J7DLVmJNlnrI5aufk5IRpplnJeKk0r10cdcwzklCSF7zihRQQBdwb1u+Pfu7jDoi1Ye+1l/h6Ph49HrJZe/FiiR923732Xk6nli1bpvT0dDmdzkDnAADgV16/Z1tQUJDKy8ur/FxhYaHCwsLqHAUAAACg/nA6nZo8ebIkad++fQoOrtN12gAAsDWvf8u1a9dOX3zxhbp3717pc9u2bVNiYqJPwgAAAADUD0FBQerbt68aNmyooCCvX1wDAMAVxevfdAMHDtSOHTs0a9Ys7dy5U9JPz04tWLBA27dv18CBA30eCQAAAODKFRYWpkWLFmnNmjW8EgYAUO95fWZbSkqKJkyYoIULF7oX2xYsWKCIiAiNHz9eHTp08HkkAAAAAAAAcCUwtdh24cIFRUREuD/u1auXevbsqb1796qwsFCNGzdWUlISz1IBAAAAAADgqmZqsS0tLU1t27ZVcnKykpOT1bFjR4WFhemGG27wdx8AAACAK1xJSYn69eunBg0a6LPPPlN4eHigkwAA8BtTi229e/dWdna2Vq1apVWrVikoKEgJCQnq3LmzkpOTlZSUpIYNG/q7FQAAAMAVyDAMHTx4MNAZAABYwtRi22OPPSZJOnPmjPbs2aM9e/bou+++U0ZGhjIyMtSgQQO1a9fOfeZb586d/RoNAAAA4MrRsGFDZWRkKDY2lifpAQD1nlcXSGjatKl69eqlXr16SZLy8/OVlZWlrKwsZWdn6+OPP9aKFSu0dOlSryLWrl2rlStXqqCgCuiBqQAALPlJREFUQK1atVJqaqo6duxY5bZZWVmaNm1apdtnz56tli1buj/etm2bli1bppMnT6p58+Z68MEH1aNHD6+6AAAAANRdgwYN1KNHD8XHxysvL0+GYQQ6CQAAv/H6aqSXi4yMVOPGjdW4cWNFRkbq9OnTXu9j69atSk9P15gxY5SUlKT169drxowZmj17tmJjY6u93xtvvOFx0YaoqCj3n3NycvTGG2/ogQceUI8ePbRjxw7Nnj1bL774otq3b+91IwAAAAAAAGCGV4ttpaWl+v77791ns11634U2bdooOTlZw4YNq/aMtOqsXr1affr0Ud++fSVJqamp2r17t9atW6cRI0ZUe7/o6GhFRkZW+bk1a9YoJSVFgwcPliQNHjxY2dnZWrNmjSZNmuRVHwAAAIC6cblc+uyzz9SkSRP17NlTDRo0CHQSAAB+Y2qx7f3331dWVpYOHDggwzCUkJDgXlzr0KFDra8m5HK5dODAAQ0aNMjj9pSUFO3du/cX7/vkk0/K6XSqVatWGjJkiMf7xOXk5Ojee+/12L5Lly765JNPqt2f0+mU0+l0f+xwONzfl8PhMPst1UuXvn87HAda7NtxeQMtnuzSYpeOyxto8WSXFrt0XN5Aiye7tNil4/IGWjzZpcXpdOrRRx+VJO3fv1/BwXV6gU2d2OWYXN5Aiye7tNil4/IGWjzZpcUuHZc30BJYpn7LZWRkKCwsTPfcc4/uv/9+j5ds1kVRUZEqKioUHR3tcXt0dLQKCgqqvE+TJk00btw4JSQkyOVy6YsvvtD06dP1wgsvqFOnTpKkgoICxcTEeNwvJiam2n1K0ooVK/Thhx+6P27btq1mzpypZs2a1ep7q4/i4uICneBGS2V26ZBoqY5dWuzSIdFSHbu02KVDoqU6dmmxS4dES3UC3VJSUqLevXtLkuLj42v9ZL0vBfqYXI6WqtmlxS4dEi3VsUuLXTokWgLN1GLb3XffrezsbK1atUqffvqpEhIS1KlTJyUnJyspKanOVxSqapWzupXPFi1aqEWLFu6PExMTlZ+fr1WrVrkX26piGMYvrqYOHjxYAwYMqPT1T58+LZfLVeP3UJ85HA7FxcXpxIkTAX8zW1rs20GL/Vvs0kGL/Vvs0kGL/Vvs0kHLldGydOlSd8svPQnub3Y6JrTYu8UuHbTYv8UuHbT4X3BwsKmTskwtto0ePVqSVFxcrOzsbGVlZWnXrl3KyMhQgwYN1LZtWyUnJ6tTp07q0KGDwsLCTEVGRUUpKCio0i/bwsLCSme7/ZLExER9+eWX7o+rOoutpn2GhIQoJCSkys/Vlx+KujIMwzbHghb7dki0VMcuLXbpkGipjl1a7NIh0VIdu7TYpUOipTq02LdDoqU6dmmxS4dES3Xs0mKXDomWQPPqzRIaNWqkHj16qEePHpJ+Wny7dLGEXbt2aeXKlQoKCtKSJUvMffHgYCUkJCgzM9O9T0nKzMxU9+7dTXcdPHjQ42WjiYmJ+vbbbz3OVMvMzFRiYqLpfQIAAAAAAADeCqrLnV0ul/s/p9MpwzBUXl7u1T4GDBigDRs2aOPGjTp69KjS09OVn5+vfv36SZKWLFmiuXPnurdfs2aNduzYoby8PB05ckRLlizR9u3bdffdd7u3ueeee7R7925lZGTo2LFjysjI0LffflvpogkAAAAA/K+kpES//vWv1bVrV5WUlAQ6BwAAv/LqzLbCwkL3mWzZ2dk6fvy4JCkoKEht2rTRfffd53FVUDNuvfVWnTt3Th999JHOnj2r1q1b6+mnn3a/Bvbs2bPKz893b+9yubRo0SKdOXNGoaGhat26tZ566indeOON7m2SkpI0adIkLV26VMuWLVNcXJwmTZqk9u3be9UGAAAAoO4Mw1B2drb7zwAA1GemFtvefvttZWVluRfXHA6Hrr/+et17771KTk5Wx44dFRERUeuI/v37q3///lV+bsKECR4fDxw4UAMHDqxxnz179lTPnj1r3QQAAADANxo2bKj3339f11xzTZ0vrgYAgN2ZWmz7/PPP1bp1a919993uCyE0atTI320AAAAA6oEGDRqod+/eio+PV15eHme3AQDqNVOLbX/5y18UFRXl7xYAAAAA1Xj3ZGjt7ngiX1KI13d7uPnF2n09AACucqYW21hoAwAAAFBbLpdLW7ZsUdOmTZWSkqIGDRoEOgkAAL/x6gIJAAAAAOCtixcv6uGHH5Yk7du3T+Hh4QEuAgDAf1hsAwAAAOBXDodDXbp0UUhIiBwOR6BzAADwKxbbAAAAAPhVeHi4Pv30Uy6QAAC4KgQFOgAAAAAAAACoL+q02Hbx4kWdOXNG5eXlvuoBAAAAAAAArli1Wmzbs2ePnn32WT388MMaP368Dh06JEl6++23tX37dp8GAgAAALiylZSU6P7779dtt92mkpKSQOcAAOBXXi+27dmzRy+99JKcTqfuu+8+j/dbiIqK0ubNm33ZBwAAAOAKZxiGdu7cqa1bt/J+bQCAes/rCyQsW7ZM3bp105NPPqny8nKtXLnS/bnrr79emzZt8mkgAAAAgCtbaGioFixYoKZNmyo0NDTQOQAA+JXXZ7bl5ubq17/+tSRVumx3VFSUioqKfFMGAAAAoF4IDg7Wb37zGw0aNEjBwV4/3w8AwBXF68W2oKCgai+IUFhYqLCwsDpHAQAAAAAAAFcirxfb2rVrpy+++KLKz23btk2JiYl1jgIAAABQf5SXl2vr1q3avHlztU/cAwBQX3i92DZw4EDt2LFDs2bN0s6dOyVJ+/bt04IFC7R9+3YNHDjQ55EAAAAArlxlZWUaOnSo7rzzTpWVlQU6BwAAv/L6DRNSUlI0YcIELVy40L3YtmDBAkVERGj8+PHq0KGDzyMBAAAAXLkcDocSExN5vzYAwFWhVr/tevXqpZ49e2rv3r0qLCxU48aNlZSUxPu1AQAAoM7ePVnLq1WeyJcU4vXdHm5+sXZfD6aFh4dr8+bNio+PV15engzDCHQSAAB+U+unlkJDQ3XDDTf4sgUAAAAAAAC4otV6se3IkSM6ffq0nE5npc/dfPPNdYoCAAAAAAAArkReL7adOHFCr7/+ug4dOlTtNsuWLatTFAAAAID6o6SkRKNHj1bDhg311ltv8fYzAIB6zevFtvnz56ugoECjRo1Sq1ateJNTAAAAAL/IMAx9+eWX7j8DAFCfeb1Stm/fPj366KO67bbb/NEDAAAAoJ4JDQ3V3LlzFRMTo9DQWl4AAwCAK4TXi21RUVGKiIjwRwsAAACAeig4OFhDhgzhaqQAgKuC14ttd911lzZs2KBu3br5LGLt2rVauXKlCgoK1KpVK6Wmpqpjx45Vbrt9+3atW7dOubm5crlcatWqlYYNG6auXbu6t9m8ebPefPPNSvddvHgxz6QBAAAAAADAb7xebLv//vv17rvvaurUqerWrZsaNWpUaZsBAwaY3t/WrVuVnp6uMWPGKCkpSevXr9eMGTM0e/ZsxcbGVtr+u+++U0pKih588EFFRkZq06ZNmjlzpmbMmKG2bdu6twsPD9ef//xnj/uy0AYAAABYr7y8XHv27NHRo0cVHx+voKCgQCcBAOA3Xi+2/fDDD9qyZYuKi4uVm5tb5TbeLLatXr1affr0Ud++fSVJqamp2r17t9atW6cRI0ZU2j41NdXj4xEjRmjnzp365ptvPBbbHA6HYmJiTHc4nU45nU6P+4eHh7v/fDW79P3b4TjQYt+Oyxto8WSXFrt0XN5Aiye7tNil4/IGWjzZpcUuHXXl636OS2UXL17UPffcI0nav3+/+3F2INjp74eWqtmlxS4dlzfQ4skuLXbpuLyBlsDyerHtr3/9qxo3bqzHHntMLVu2rNPVSF0ulw4cOKBBgwZ53J6SkqK9e/ea2kdFRYVKSkoqnWFXWlqq8ePHq6KiQm3atNEDDzzgsRj3cytWrNCHH37o/rht27aaOXOmmjVrZv4bqufi4uICneBGS2V26ZBoqY5dWuzSIdFSHbu02KVDoqU6dmnxeceJfN/urwbx8fF+2S/H5f9cuHBB119/vaSfjosd3gPaLv9+JFqqY5cWu3RItFTHLi126ZBoCTSvV8qOHDmiSZMm6aabbqrzFy8qKlJFRYWio6M9bo+OjlZBQYGpfaxevVplZWW65ZZb3Le1aNFC48eP13XXXaeSkhJ98skneu655zRr1qxqHzQMHjzY44y8Syuvp0+flsvl8vI7q18cDofi4uJ04sSJgL+ZLS327aDF/i126aDF/i126aDF/i3+6wjx4b5qlpeX59P9cVyqtm3bNvdxKSws9Om+vWGXfz+02L/FLh202L/FLh20+F9wcLCpk7K8XmyLjY31+UGq6pRCM6cZfvXVV1q+fLmmTJnisWCXmJioxMRE98dJSUmaOnWqPv30U6WlpVW5r5CQEIWEVP0Apr78UNSVYRi2ORa02LdDoqU6dmmxS4dES3Xs0mKXDomW6tilxS4dteWvdo5L9fu1w3GxS4dES3Xs0mKXDomW6tilxS4dEi2B5vU7kw4aNEirVq3SxYsX6/zFo6KiFBQUVOkstsLCwkpnu/3c1q1b9T//8z+aPHmyUlJSfnHboKAg/epXv9KJEyfqmgwAAAAAAABUy+sz2w4cOKAzZ87o3//935WcnFzpvdIcDodGjx5t7osHByshIUGZmZnq0aOH+/bMzEx179692vt99dVX+u///m9NnDhRN954Y41fxzAMHTp0SK1btzbVBQAAAMB3Lr2fclhYmGbPnq2GDRsGOgkAAL/xerFt7dq17j9//fXXVW5jdrFN+unKpXPmzFFCQoISExO1fv165efnq1+/fpKkJUuW6MyZM3r88ccl/bTQNm/ePKWmpioxMdF9VlxoaKj7jVaXL1+u9u3bKz4+3v2ebbm5uXrkkUe8/XYBAABwFXv3ZGjt7ngiX7V5j7WHm9f91SN2VFFR4f7/iNdeey3ANQAA+JfXi23Lli3zacCtt96qc+fO6aOPPtLZs2fVunVrPf300+43nDt79qzy8//vykvr169XeXm5FixYoAULFrhv7927tyZMmCBJOn/+vObPn6+CggJFRESobdu2mjZtmtq1a+fTdgAAAAA1CwkJ0axZsxQdHV3t+yQDAFBfeL3Y5g/9+/dX//79q/zcpQW0S/74xz/WuL/U1FSlpqb6oAwAAABAXYWEhGjkyJGKj49XXl7eVfdG2QCAq4vXF0gAAAAAAAAAUDVTZ7Y9/vjjeuKJJ9SmTRtNmDBBDoej2m0dDofmzJnjs0AAAAAAV7aKigrt27dPZ86cUUxMzC/+/wQAAFc6U4ttnTp1cl98oFOnTvxyBAAAAGBaaWmp7rzzTknSvn37FB4eHuAiAAD8x9Ri2x133KGoqChJld9DDQAAAABq0rRpUwUF8S42AID6z9Rvu2nTpuno0aP+bgEAAABQD0VERGjPnj06ffq0+xUzAADUVzy1BAAAAAAAAPgIi20AAAAAAACAj7DYBgAAAMCvSktLNWHCBI0cOVKlpaWBzgEAwK9MXSBB+ul928y+oenChQtrHQQAAACgfqmoqNCKFSskSS+++GKAawAA8C/Ti23JycnuK5ICAAAAgFkhISGaNm2aoqKiFBISEugcAAD8yvRi29ChQ9WuXTt/tgAAAACoh0JCQjR27FjFx8crLy9PhmEEOgkAAL/hPdsAAAAAAAAAHzF9ZhsAAAAA1EZFRYWOHz+usrIyhYSEyOFwBDoJAAC/4cw2AAAAAH5VWlqqm2++WW3btuVqpACAes/UmW3Lli3zdwcAAACAeiw8PJwz2gAAVwXObAMAAADgVxEREdq/f7/Onz+viIiIQOcAAOBXLLYBAAAAAAAAPsJiGwAAAAAAAOAjLLYBAAAA8KuysjI98cQTGjt2rMrKygKdAwCAX7HYBgAAAMCvysvLtWTJEr399tsqLy8PdA4AAH5l6mqkAAAAAFBbwcHBmjp1qho3bqzgYP4XBABQv/GbDgAAAIBfhYaGauLEiYqPj1deXp4Mwwh0EgAAfmOLxba1a9dq5cqVKigoUKtWrZSamqqOHTtWu312drYWLlyoo0ePqkmTJrr//vt11113eWyzbds2LVu2TCdPnlTz5s314IMPqkePHv7+VgAAAAAAAHAVC/h7tm3dulXp6ekaMmSIZs6cqY4dO2rGjBnKz8+vcvtTp07p5ZdfVseOHTVz5kwNHjxY77zzjrZt2+beJicnR2+88YZ69eqlWbNmqVevXpo9e7Z++OEHq74tAAAAAP+fYRj68ccfdfr0ac5qAwDUewE/s2316tXq06eP+vbtK0lKTU3V7t27tW7dOo0YMaLS9uvWrVNsbKxSU1MlSa1atdL+/fu1atUq9ezZU5K0Zs0apaSkaPDgwZKkwYMHKzs7W2vWrNGkSZMs+b4AAACuNO+eDPX+TifyJYV4fbeHm1/0/mvhilVSUqIbbrhBkrRv3z6Fh4cHuAgAAP8J6GKby+XSgQMHNGjQII/bU1JStHfv3irv88MPPyglJcXjtq5du2rTpk1yuVwKDg5WTk6O7r33Xo9tunTpok8++aTaFqfTKafT6f7Y4XAoPDycN3DVT8dCkkJCQgL+TCQt9u2gxf4tdumgxf4tdum4WlpW/1jLxxqFZyWFeX23Ade4qv1c8/AGtWuphZCQ6hforOyQaKnOL7V4q2HDhurWrZv7z77ct7euhrlCS/3qoMX+LXbpoMX/zK4RBXQlqaioSBUVFYqOjva4PTo6WgUFBVXep6CgoMrty8vLde7cOTVp0kQFBQWKiYnx2CYmJqbafUrSihUr9OGHH7o/vu222zRx4kQ1adLEq++pPouNjQ10ghstldmlQ6KlOnZpsUuHREt17NJilw6pfreMbubT3dWJXVrs0iHR4ku7du0KdIKH+jxX6oKWyuzSIdFSHbu02KVDoiXQAv6ebdL/rXbWdFt1n7u0QvpL9zEM4xc/P3jwYKWnp7v/Gzt2rMeZblezkpISTZ06VSUlJYFOocXGHbTYv8UuHbTYv8UuHbTYv8UuHbTQciV20GL/Frt00GL/Frt00GIfAT2zLSoqSkFBQZXOOCssLKx09tolVZ2hVlRUpAYNGqhRo0bVbvNL+5R+Oq0xkKez25lhGDp48KAtTvukxb4dtNi/xS4dtNi/xS4dtNi/xS4dtNByJXbQYv8Wu3TQYv8Wu3TQYh8BPbMtODhYCQkJyszM9Lg9MzNTSUlJVd6nffv2lbbfvXu3EhIS3K+dTUxM1Lfffltpn4mJiT6sBwAAAAAAADwF/GWkAwYM0IYNG7Rx40YdPXpU6enpys/PV79+/SRJS5Ys0dy5c93b33XXXcrPz9fChQt19OhRbdy4URs3btR9993n3uaee+7R7t27lZGRoWPHjikjI0PffvttpYsmAAAAAAAAAL4U8Ett3nrrrTp37pw++ugjnT17Vq1bt9bTTz+tZs1+egfYs2fPKj8/3739tddeq6effloLFy7U2rVr1aRJE40ePVo9e/Z0b5OUlKRJkyZp6dKlWrZsmeLi4jRp0iS1b9/e8u+vPggJCdHQoUNt8TJbWuzbQYv9W+zSQYv9W+zSQYv9W+zSQQstV2IHLfZvsUsHLfZvsUsHLfbhMK7GF88CAAAAAAAAfhDwl5ECAAAAAAAA9QWLbQAAAAAAAICPsNgGAAAAAAAA+AiLbQAAAAAAAICPBPxqpLBGdna2Vq5cqYMHD+rs2bN64okn1KNHD/fnCwoK9N577ykzM1Pnz59Xx44dlZaWpvj4eEnSqVOn9Pjjj1e578mTJ+uWW26RJBUXF+udd97Rzp07JUk33XST0tLSFBkZaXnLxx9/rF27dik3N1fBwcFKT0+3/JicOnVKH330kfbs2aOCggI1bdpU//qv/6ohQ4YoOPj//vlZdUxmzpyp3NxcFRUVKTIyUjfccINGjhyppk2bWt5yidPp1DPPPKNDhw7p1VdfVZs2bSxvmTBhgk6fPu3x+YEDB2rkyJGWH5Ndu3bpww8/1KFDhxQWFqaOHTvqiSeesPSYZGVladq0aVVuM2PGDLVr187S43L8+HEtXrxYe/fulcvl0nXXXacHHnhAnTt3tvS4SNKBAwf03nvvaf/+/QoKCtLNN9+sUaNGKSwszCcdl7ZZtGiRMjMzVVpaqhYtWmjw4MEeV922Ytaabalp1lrVYtW8NXtcrJi3ZlsuqW7eWtVR06y1+pj4e96aabFq3po9LjXNW6s6apq1vmo5ceKEFi1apO+//14ul0tdunRRWlqaYmJi3NtYNW/NtFg1b2tqMTNvrTomVs1aMy2X+POxrdkWq+at2ePyS/PWig4rZ62ZY2LFY1uzLTXN2xUrVmjHjh06duyYQkNDlZiYqIceekgtWrRw78MwDC1fvlwbNmxQcXGx2rdvr0ceeUStW7d2b+N0OrVo0SJ9/fXXunjxojp37qwxY8bommuucW9jZt5eSTiz7SpRVlamNm3aKC0trdLnDMPQrFmzdOrUKU2ZMkWvvvqqmjVrpunTp6u0tFSSFBsbq/nz53v8N3z4cDVs2FDdunVz7+u//uu/lJubq2effVbPPvuscnNzNWfOnIC0uFwu9ezZU3fddVfAjsnx48dlGIbGjRun119/XaNGjdLnn3+uJUuWBOSYJCcna/LkyXrjjTf0+9//XidPntTrr78ekJZLFi9e7PGAKFAtw4cP99jut7/9reUd27Zt05w5c3THHXdo1qxZmj59um6//XbLj0lSUlKlbfr06aNmzZrpV7/6leXH5ZVXXlF5ebmef/55vfLKK7r++us1c+ZMFRQUWNpy5swZTZ8+XXFxcZoxY4aeeeYZHT16VPPmzfNZhyTNmTNHx48f19SpU/Wf//mf6tGjh2bPnq2DBw+6t7Fi1pptqWnWWtVi1bw1e1ysmLdmWy6pbt5a2fFLs9bKFivmrZkWq+at2eNS07y1osPMrPVFS2lpqV566SU5HA698MILmj59ulwul2bOnKmKigr3vqyYt2ZbrJi3ZlrMzFurjokVs9ZsyyX+fGzrTYu/563ZlprmrRUdVs1as8fEise2ZlrMzNvs7Gz1799fL730kv7whz+ooqJCf/rTnzzm+t/+9jetWbNGaWlpevnllxUTE6M//elPKikpcW+Tnp6uHTt2aOLEiXrxxRdVWlqqV155xet5e0UxcNUZNmyYsX37dvfHx44dM4YNG2YcPnzYfVt5ebkxevRoY/369dXuZ8qUKcabb77p/vjIkSPGsGHDjJycHPdte/fuNYYNG2YcO3bM0pbLbdq0yRg1alS197Wq45K//e1vxoQJE2zR8o9//MMYPny44XQ6A9Kya9cuY9KkSe6fnYMHD1a7D3+2jB8/3li9enW197Giw+VyGY8++qixYcMGUx3+bPk5p9NpjBkzxli+fLnlLYWFhcawYcOM7Oxs920XLlwwhg0bZmRmZlra8vnnnxtjxowxysvL3bcdPHjQGDZsmJGXl+ezjoceesjYsmWLx75Gjx7t/tmwctbW1HI5M7PWqpZL/DVva9Pir3lrtsXsvPVnhzez1p8tVs5bb39W/Dlva2rxdt76q8PbWVvbln/+85/G8OHDjfPnz7u3OXfunDFs2DBj9+7dhmFYN2/NtFzOn/PW25ZLfmneWtnhj1nrTYu/H9uabbFi3ppp8XbeWvWz4q9Za6bFqse2ZlpqM28v9WdlZRmGYRgVFRXG2LFjjRUrVri3uXjxojFq1Chj3bp1hmEYxvnz541/+7d/M77++mv3Nj/++KMxfPhw43//938Nw6jdvLU7zmyDXC6XJCkkJMR9W1BQkIKDg/X9999XeZ8DBw4oNzdXffr0cd+Wk5OjiIgItW/f3n1bYmKiIiIitHfvXktb6sqfHRcuXFCjRo0C3lJcXKwvv/xSiYmJHi+xsqqloKBAb731lh5//HGFhoaa+vr+apF+ekYmLS1NU6ZM0ccff+zev1UdBw8e1JkzZ+RwOPTkk09q3LhxmjFjho4cOWKqw5ctP7dz504VFRXpjjvusLylcePGatmypbZs2aLS0lKVl5fr888/V3R0tBISEixtcTqdCg4OVlDQ//3qvPSzW91+atPRoUMHbd26VcXFxaqoqNDXX38tp9OpTp06SbJ21tbU4gv+bPHXvPW2xZ/z1kxLXeatr49JbWetL1usnLfe/qz4c97W1FLXeeurjrrOWrMtTqdTDofDY5vQ0FA5HA73NlbNWzMtvuDPFm/mrb86/DVrzbZY8djWm+Pi73lrpqWu89ZfPyv+mrVmWqx6bGumpTbz9sKFC5Lk/vd+6tQpFRQUqEuXLu5tQkJC1KlTJ/ecPHDggMrLy5WSkuLepmnTprruuuuUk5MjyTfz1m5YbINatGihZs2aacmSJSouLpbL5VJGRoYKCgo8TmW93MaNG9WyZUslJSW5bysoKFB0dHSlbaOjo6vdj79a6spfHSdOnNCnn36qfv36Baxl8eLF+t3vfqe0tDTl5+frySeftLzFMAy9+eab6tevn8ep297w5XH5zW9+o0mTJumFF17Q3XffrTVr1ujtt9+2tOPkyZOSpOXLl2vIkCF66qmnFBkZqT/+8Y8qLi62tOXnNm3apK5duyo2NtZUhy9bHA6HnnvuOeXm5mrUqFEaOXKk1qxZo2effdb0+zf4qqVz584qKCjQypUr5XK5VFxc7H7JzNmzZ33WMXnyZJWXlystLU0jR47U/PnzNWXKFMXFxUmydtbW1OIL/mrx57w122LFvK2ppa7z1pfHpC6z1pctVs5bb39u/Tlva2qp67z1VUddZ63ZlsTERDVs2FDvvfeeysrKVFpaqkWLFskwDPc2Vs1bMy2+4K8Wb+etrzv8PWvNtFj12NbscbFi3pppqeu89dfPrL9mrZkWqx7bmmnxdt4ahqGFCxeqQ4cOuu666yTJva+fz8ro6GgVFha6twkODq60IH/5LPXFvLUbFtug4OBg/f73v1deXp7S0tL00EMPKSsrS926dfNY5b7k4sWL+uqrr0yfSWYYhhwOhy1azPJHx5kzZzRjxgzdcsst6tu3b8Ba7r//fs2cOVN/+MMfFBQUpLlz58owDEtbPv30U5WUlGjw4MGmvq4/WyRpwIAB6tSpk66//nr17dtXY8eO1caNG3Xu3DnLOi79HQwZMkQ9e/ZUQkKCxo8fL0n6+9//bvkxueTHH3/UP//5T915552mGnzdYhiG3n77bUVHR2vatGmaMWOGunfvrldeecX0/3T5qqV169aaMGGCVq1apYceekjjxo1T8+bNFR0dXeV+atuxdOlSnT9/Xs8995xefvllDRgwQK+//roOHz78i/v3x6ytbYs3/NHi73lrtsWKeVtTS13nrS+PSV1mrS9brJy33vzc+nvemjkudZm3vuqo66w12xIVFaX/+I//0DfffKOHH35YqampunDhgtq2bVvj1/H1vK1Lizf80VKbeevrDn/PWjMtVj22NXtcrJi3ZlrqOm/98TPrz1lr9phY8djWTIu383bBggU6fPiwJk6cWOlzP5+JZv4Nmt3G7Ly1G65GCklSQkKCZs2apQsXLsjlcikqKkrPPPNMlaeybtu2TWVlZerdu7fH7TExMe7V68sVFRVVuUrtzxZf8GXHmTNnNG3aNCUmJmrcuHEBbYmKilJUVJRatGihli1b6rHHHtMPP/ygxMREy1r27NmjnJwcjRgxwuP2p556Srfffnu1V4X0R0tVLh2LEydOqHHjxpZ0XLoqUKtWrdy3hYSEqHnz5srPz6+xwZctl9u0aZMaN26sm266yXSDL1v27Nmjb775Ru+8844iIiLc+83MzNSWLVs0aNAgy1ok6fbbb9ftt9+ugoIC91WaVq9erWuvvdYnHSdOnNBnn32m1157zX0FpzZt2uj777/XZ599pnHjxlk2a820+IovW/w9b71p8fe8NdPii3nrr58Vb2etr1qsmrfeHhd/zluzPyt1nbe+OiZ1nbVmWiSpS5cumjNnjoqKitSgQQNFRkZq7Nix7q9j5WPbmlp8xZctdZm3vuyw4rFtTS1WPratzc+KP+atmRZfzFtfHxN/P7Y187Ni1WNbM8fF7Lz961//qm+++UbTpk3zuILopb/jgoICNWnSxH375XMyJibGfebc5We3FRUVuV9F4qt5ayec2QYPERERioqKUl5envbv36/u3btX2mbjxo266aabFBUV5XF7YmKiLly4oH379rlv++GHH3ThwoVavcSzLi2+VNeOSw9E2rZtq/Hjx9fpGUpfH5NLzyY4nU5LW9LS0jRr1iy9+uqrevXVV/X0009LkiZNmqQHH3zQ0paqXLoi2uW/MPzdkZCQoJCQEB0/ftx9m8vl0unTp9WsWTOvOuracolhGNq8ebN69epl+r1PfN1SVlYmSZX+3TgcjiqvAObPlsvFxMQoLCxMW7duVWhoqMd7UNSl4+LFi5IqPzsYFBTk/vdq1aw10+JrdW2xYt7W9rj4Y96aafHlvPX1MantrK1ri1Xz1pvj4u95a6bFl/PWVz8rdZ21v9RyuaioKEVGRmrPnj0qKipy/094IB7bVtfia3Vt8dW89fUx8fdj2+paAvHY1pvj4o95a6bFl/PWF8fEyse21bUE4rGtmZ+V6uatYRhasGCBtm/frueff77SIty1116rmJgYZWZmum9zuVzKzs52z8mEhAQ1aNDAY5uzZ8/q8OHD7oVgX89bO+DMtqtEaWmpTpw44f741KlTys3NVaNGjRQbG6u///3vioqKUmxsrA4fPqz09HR1797d440OpZ+eDfnuu+/cv0Au16pVK3Xt2lVvvfWWxo4dK0maP3++brzxRrVo0cLSFknKz89XcXGx8vPzVVFRodzcXElSXFycwsLCLOk4c+aM/vjHPyo2NlYPP/ywioqK3J+79CyAVcdk37592rdvnzp06KDIyEidPHlSH3zwgZo3b+7xzJ8VLT9/f4RLz6LExcV5PFNiRUtOTo5ycnLUuXNnRUREaN++fVq4cKFuuukmd6cVHREREerXr58++OADXXPNNWrWrJlWrlwpSerZs6elx+SSPXv26NSpU9W+zNSKlsTERDVq1Ehz587V0KFDFRoaqg0bNujUqVO68cYbLT8un332mRITExUWFqbMzEwtXrxYI0aMcL/HRl07WrRoobi4OP3lL3/R7373OzVq1Ej/+Mc/lJmZqalTp0qybtaaaZFqnrVWtVg1b820WDVvzbSYmbdWdJiZtVa1WDVvzf4bkvw/b820mJm3Vh2TmmatL1qkn85wadmypaKiopSTk6P09HTde++97llq5WPbmloka+atmRYz89aKDisf29bUYuVj25parJq3ZlrMzFur/v1I1jy2ranFyse2Zo5LTfN2wYIF+uqrr/Tkk08qPDzc/f5pERER7gsu3HPPPVqxYoXi4+MVFxenFStWqGHDhrr99tvd2/bp00eLFi1S48aN1ahRIy1atEjXXXede1HP7Ly9kjgMfz09DVvJysrStGnTKt3eu3dvTZgwQZ988olWrVrlPv2zV69eGjp0aKUV/yVLlujLL7/UvHnzqnwGq7i42H2KqST9y7/8ix555BGPB0dWtcybN09btmypdPsLL7yg5ORkSzo2b96sN998s9LXkKQPPvjA0mNy+PBhvfPOOzp06JDKysoUExOjrl276re//a2aNm1qacvPnTp1So8//rheffVVtWnTxtKWAwcOaMGCBTp27JicTqeaNWumW2+9VQMHDlTDhg0tPSYul8u9zcWLF9WuXTulpqa6X2pjZYsk/fnPf1Z+fr6mT59e5eetatm/f7+WLl2q/fv3q7y8XK1atdLQoUPVrVs3y1vmzp2rXbt2qbS0VC1bttR9992nXr16+bQjLy9P7733nvbu3avS0lLFxcVV+jpWzVozLTXNWqtarJy3NbVYOW/N/B1drqp5a0WHmVlr5TGxat6a/fuxYt6aaalp3lrVUdOs9VXLe++9p82bN6u4uFjXXnut+vXrp3vvvdfjzDur5q2ZFqvmbU0tZuatFR1Wzlozfz+X8+dj25parJy3Zo5LTfPWyr8fK2atmRarHtuaaalp3g4fPrzKYzV+/Hj31VwNw9Dy5cu1fv16nT9/Xu3atdMjjzzivoiC9NOZzYsXL9ZXX32lixcvqnPnzhozZozHArCZeXslYbENAAAAAAAA8BHesw0AAAAAAADwERbbAAAAAAAAAB9hsQ0AAAAAAADwERbbAAAAAAAAAB9hsQ0AAAAAAADwERbbAAAAAAAAAB9hsQ0AAAAAAADwERbbAAAAAAAAAB8JDnQAAAAAfGv48OGmtnvhhRe0efNmZWdna968eX6uAgAAuDo4DMMwAh0BAAAA38nJyfH4+KOPPlJWVpaef/55j9tbtWqloqIilZSUqG3btlYmAgAA1Fuc2QYAAFDPJCYmenwcFRUlh8NR6XZJioiIsCoLAADgqsBiGwAAwFVs3rx5lV5GOnz4cPXv318JCQnKyMhQfn6+WrdurbS0NLVv316rVq3S2rVrVVRUpHbt2unRRx9VXFycx34zMzOVkZGh/fv3q7y8XG3bttXw4cN1ww03WP0tAgAAWIoLJAAAAKCSXbt2aePGjRo5cqQmTpyokpISvfLKK3r33Xf1/fff65FHHtGjjz6qo0eP6rXXXtPl70zyxRdf6KWXXlJ4eLgmTJigyZMnKzIyUi+99JK+/fbbAH5XAAAA/seZbQAAAKjE6XTq2WefVVhYmCTJ4XBo1qxZysrK0syZM+VwOCRJRUVFSk9P15EjR3TdddeprKxM6enpuvHGGzVlyhT3/rp166apU6fq/fff5+w2AABQr3FmGwAAACpJTk52L7RJUsuWLSVJXbt2dS+0XX776dOnJUl79+5VcXGxevfurfLycvd/hmGoa9eu2r9/v0pLSy38TgAAAKzFmW0AAACopFGjRh4fBwcH/+LtTqdTklRYWChJev3116vdd3FxscdCHgAAQH3CYhsAAAB8pnHjxpLkvphCVWJiYiwsAgAAsBaLbQAAAPCZDh06KDIyUkePHtXdd98d6BwAAADLsdgGAAAAnwkLC9Po0aM1b948FRcXq2fPnoqKilJRUZEOHTqkoqIijR07NtCZAAAAfsNiGwAAAHyqV69eio2N1cqVKzV//nyVlJQoOjpabdq00R133BHoPAAAAL9yGIZhBDoCAAAAAAAAqA+CAh0AAAAAAAAA1BcstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CP/D7BtpE2/lsyCAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15,8), sharex=True, gridspec_kw={'height_ratios': [3, 1]})\n", + "fig.suptitle(\"Synthetic Diff in Diff Estimation\")\n", + "\n", + "ax1.plot(pivot_df_control.mean(axis=1), lw=3, color=\"C1\", ls=\"dashed\", label=\"Control Avg.\")\n", + "ax1.plot(treated_mean, lw=3, color=\"C0\", label=\"California\")\n", + "ax1.plot(pivot_df_control.index, sc_did, label=\"Synthetic Control (SDID)\", color=\"C1\", alpha=.8)\n", + "ax1.set_ylabel(\"Cigarette Sales\")\n", + "ax1.vlines(1989, treated_mean.min(), treated_mean.max(), color=\"black\", ls=\"dotted\", label=\"Prop. 99\")\n", + "ax1.legend()\n", + "\n", + "ax2.bar(time_weights.index, time_weights['value'], color='skyblue')\n", + "ax2.set_ylabel(\"Time Weights\")\n", + "ax2.set_xlabel(\"Time\");\n", + "ax2.vlines(1989, 0, 1, color=\"black\", ls=\"dotted\")" + ] + } + ], + "metadata": { + "kernel_info": { + "name": "synapse_pyspark" + }, + "kernelspec": { + "display_name": "Synapse PySpark", + "language": "Python", + "name": "synapse_pyspark" + }, + "language_info": { + "name": "python" + }, + "save_output": true, + "synapse_widget": { + "state": { + "7cfbe0f7-9f3c-44c3-828d-922a7bff621e": { + "persist_state": { + "view": { + "chartOptions": { + "aggregationType": "sum", + "categoryFieldKeys": [ + "1" + ], + "chartType": "bar", + "isStacked": false, + "seriesFieldKeys": [ + "0" + ] + }, + "tableOptions": {}, + "type": "details" + } + }, + "sync_state": { + "isSummary": false, + "language": "scala", + "table": { + "rows": [ + { + "0": "1", + "1": "1970", + "2": "89.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1971", + "2": "95.4000015258789", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1972", + "2": "101.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1973", + "2": "102.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1974", + "2": "108.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1975", + "2": "111.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1976", + "2": "116.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1977", + "2": "117.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1978", + "2": "123.0", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1979", + "2": "121.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1980", + "2": "123.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1981", + "2": "119.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1982", + "2": "119.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1983", + "2": "116.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1984", + "2": "113.0", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1985", + "2": "114.5", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1986", + "2": "116.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1987", + "2": "114.0", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1988", + "2": "112.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "1", + "1": "1989", + "2": "105.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1990", + "2": "108.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1991", + "2": "107.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1992", + "2": "109.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1993", + "2": "108.5", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1994", + "2": "107.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1995", + "2": "102.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1996", + "2": "101.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1997", + "2": "104.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1998", + "2": "106.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "1999", + "2": "100.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "1", + "1": "2000", + "2": "96.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1970", + "2": "100.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1971", + "2": "104.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1972", + "2": "103.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1973", + "2": "108.0", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1974", + "2": "109.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1975", + "2": "114.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1976", + "2": "119.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1977", + "2": "122.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1978", + "2": "127.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1979", + "2": "126.5", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1980", + "2": "131.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1981", + "2": "128.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1982", + "2": "127.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1983", + "2": "128.0", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1984", + "2": "123.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1985", + "2": "125.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1986", + "2": "126.0", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1987", + "2": "122.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1988", + "2": "121.5", + "3": "false", + "4": "false" + }, + { + "0": "2", + "1": "1989", + "2": "118.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1990", + "2": "113.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1991", + "2": "116.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1992", + "2": "126.0", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1993", + "2": "113.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1994", + "2": "108.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1995", + "2": "113.0", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1996", + "2": "110.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1997", + "2": "108.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1998", + "2": "109.5", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "1999", + "2": "104.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "2", + "1": "2000", + "2": "99.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "3", + "1": "1970", + "2": "123.0", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1971", + "2": "121.0", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1972", + "2": "123.5", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1973", + "2": "124.400001525879", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1974", + "2": "126.699996948242", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1975", + "2": "127.099998474121", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1976", + "2": "128.0", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1977", + "2": "126.400001525879", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1978", + "2": "126.099998474121", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1979", + "2": "121.900001525879", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1980", + "2": "120.199996948242", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1981", + "2": "118.599998474121", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1982", + "2": "115.400001525879", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1983", + "2": "110.800003051758", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1984", + "2": "104.800003051758", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1985", + "2": "102.800003051758", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1986", + "2": "99.6999969482422", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1987", + "2": "97.5", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1988", + "2": "90.0999984741211", + "3": "true", + "4": "false" + }, + { + "0": "3", + "1": "1989", + "2": "82.4000015258789", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1990", + "2": "77.8000030517578", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1991", + "2": "68.6999969482422", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1992", + "2": "67.5", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1993", + "2": "63.4000015258789", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1994", + "2": "58.5999984741211", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1995", + "2": "56.4000015258789", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1996", + "2": "54.5", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1997", + "2": "53.7999992370605", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1998", + "2": "52.2999992370605", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "1999", + "2": "47.2000007629395", + "3": "true", + "4": "true" + }, + { + "0": "3", + "1": "2000", + "2": "41.5999984741211", + "3": "true", + "4": "true" + }, + { + "0": "4", + "1": "1970", + "2": "124.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1971", + "2": "125.5", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1972", + "2": "134.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1973", + "2": "137.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1974", + "2": "132.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1975", + "2": "131.0", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1976", + "2": "134.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1977", + "2": "132.0", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1978", + "2": "129.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1979", + "2": "131.5", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1980", + "2": "131.0", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1981", + "2": "133.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1982", + "2": "130.5", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1983", + "2": "125.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1984", + "2": "119.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1985", + "2": "112.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1986", + "2": "109.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1987", + "2": "102.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1988", + "2": "94.5999984741211", + "3": "false", + "4": "false" + }, + { + "0": "4", + "1": "1989", + "2": "88.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1990", + "2": "87.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1991", + "2": "90.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1992", + "2": "88.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1993", + "2": "88.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1994", + "2": "89.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1995", + "2": "85.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1996", + "2": "83.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1997", + "2": "81.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1998", + "2": "81.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "1999", + "2": "79.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "4", + "1": "2000", + "2": "73.0", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1970", + "2": "120.0", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1971", + "2": "117.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1972", + "2": "110.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1973", + "2": "109.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1974", + "2": "112.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1975", + "2": "110.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1976", + "2": "113.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1977", + "2": "117.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1978", + "2": "117.5", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1979", + "2": "117.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1980", + "2": "118.0", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1981", + "2": "116.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1982", + "2": "114.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1983", + "2": "114.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1984", + "2": "112.5", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1985", + "2": "111.0", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1986", + "2": "108.5", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1987", + "2": "109.0", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1988", + "2": "104.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "5", + "1": "1989", + "2": "100.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1990", + "2": "91.5", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1991", + "2": "86.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1992", + "2": "83.5", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1993", + "2": "79.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1994", + "2": "76.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1995", + "2": "79.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1996", + "2": "76.0", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1997", + "2": "75.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1998", + "2": "75.5", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "1999", + "2": "73.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "5", + "1": "2000", + "2": "71.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1970", + "2": "155.0", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1971", + "2": "161.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1972", + "2": "156.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1973", + "2": "154.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1974", + "2": "151.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1975", + "2": "147.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1976", + "2": "153.0", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1977", + "2": "153.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1978", + "2": "155.5", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1979", + "2": "150.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1980", + "2": "150.5", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1981", + "2": "152.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1982", + "2": "154.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1983", + "2": "149.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1984", + "2": "144.0", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1985", + "2": "144.5", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1986", + "2": "142.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1987", + "2": "141.0", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1988", + "2": "137.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "6", + "1": "1989", + "2": "131.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1990", + "2": "127.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1991", + "2": "118.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1992", + "2": "120.0", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1993", + "2": "123.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1994", + "2": "126.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1995", + "2": "127.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1996", + "2": "128.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1997", + "2": "124.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1998", + "2": "132.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "1999", + "2": "139.5", + "3": "false", + "4": "true" + }, + { + "0": "6", + "1": "2000", + "2": "140.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1970", + "2": "109.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1971", + "2": "115.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1972", + "2": "117.0", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1973", + "2": "119.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1974", + "2": "123.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1975", + "2": "122.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1976", + "2": "125.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1977", + "2": "127.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1978", + "2": "130.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1979", + "2": "131.0", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1980", + "2": "134.0", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1981", + "2": "131.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1982", + "2": "131.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1983", + "2": "128.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1984", + "2": "126.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1985", + "2": "128.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1986", + "2": "129.0", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1987", + "2": "129.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1988", + "2": "124.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "7", + "1": "1989", + "2": "117.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1990", + "2": "113.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1991", + "2": "109.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1992", + "2": "109.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1993", + "2": "109.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1994", + "2": "107.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1995", + "2": "100.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1996", + "2": "102.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1997", + "2": "100.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1998", + "2": "100.5", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "1999", + "2": "97.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "7", + "1": "2000", + "2": "88.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1970", + "2": "102.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1971", + "2": "108.5", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1972", + "2": "126.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1973", + "2": "121.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1974", + "2": "125.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1975", + "2": "123.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1976", + "2": "125.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1977", + "2": "125.0", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1978", + "2": "122.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1979", + "2": "117.5", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1980", + "2": "115.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1981", + "2": "114.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1982", + "2": "111.5", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1983", + "2": "111.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1984", + "2": "103.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1985", + "2": "100.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1986", + "2": "96.6999969482422", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1987", + "2": "95.0", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1988", + "2": "84.5", + "3": "false", + "4": "false" + }, + { + "0": "8", + "1": "1989", + "2": "78.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1990", + "2": "90.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1991", + "2": "85.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1992", + "2": "85.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1993", + "2": "86.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1994", + "2": "93.0", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1995", + "2": "78.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1996", + "2": "73.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1997", + "2": "75.0", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1998", + "2": "78.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "1999", + "2": "75.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "8", + "1": "2000", + "2": "66.90000152587889", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1970", + "2": "124.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1971", + "2": "125.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1972", + "2": "126.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1973", + "2": "124.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1974", + "2": "131.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1975", + "2": "131.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1976", + "2": "134.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1977", + "2": "134.0", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1978", + "2": "136.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1979", + "2": "135.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1980", + "2": "135.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1981", + "2": "133.0", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1982", + "2": "130.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1983", + "2": "127.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1984", + "2": "124.0", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1985", + "2": "121.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1986", + "2": "118.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1987", + "2": "109.5", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1988", + "2": "107.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "9", + "1": "1989", + "2": "104.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1990", + "2": "94.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1991", + "2": "96.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1992", + "2": "94.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1993", + "2": "94.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1994", + "2": "85.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1995", + "2": "84.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1996", + "2": "81.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1997", + "2": "79.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1998", + "2": "80.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "1999", + "2": "72.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "9", + "1": "2000", + "2": "70.0", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1970", + "2": "134.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1971", + "2": "139.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1972", + "2": "149.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1973", + "2": "156.0", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1974", + "2": "159.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1975", + "2": "162.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1976", + "2": "166.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1977", + "2": "173.0", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1978", + "2": "150.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1979", + "2": "148.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1980", + "2": "146.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1981", + "2": "148.5", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1982", + "2": "147.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1983", + "2": "143.0", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1984", + "2": "137.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1985", + "2": "135.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1986", + "2": "137.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1987", + "2": "134.0", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1988", + "2": "134.0", + "3": "false", + "4": "false" + }, + { + "0": "10", + "1": "1989", + "2": "132.5", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1990", + "2": "128.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1991", + "2": "127.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1992", + "2": "128.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1993", + "2": "126.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1994", + "2": "128.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1995", + "2": "135.399993896484", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1996", + "2": "135.100006103516", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1997", + "2": "135.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1998", + "2": "135.899993896484", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "1999", + "2": "133.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "10", + "1": "2000", + "2": "125.5", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1970", + "2": "108.5", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1971", + "2": "108.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1972", + "2": "109.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1973", + "2": "110.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1974", + "2": "116.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1975", + "2": "120.5", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1976", + "2": "124.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1977", + "2": "125.5", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1978", + "2": "127.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1979", + "2": "124.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1980", + "2": "124.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1981", + "2": "132.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1982", + "2": "116.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1983", + "2": "115.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1984", + "2": "111.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1985", + "2": "109.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1986", + "2": "104.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1987", + "2": "101.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1988", + "2": "100.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "11", + "1": "1989", + "2": "94.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1990", + "2": "95.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1991", + "2": "97.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1992", + "2": "95.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1993", + "2": "92.5", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1994", + "2": "93.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1995", + "2": "93.0", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1996", + "2": "94.0", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1997", + "2": "93.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1998", + "2": "94.0", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "1999", + "2": "91.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "11", + "1": "2000", + "2": "88.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1970", + "2": "114.0", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1971", + "2": "102.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1972", + "2": "111.0", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1973", + "2": "115.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1974", + "2": "118.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1975", + "2": "123.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1976", + "2": "127.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1977", + "2": "127.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1978", + "2": "127.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1979", + "2": "126.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1980", + "2": "127.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1981", + "2": "132.0", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1982", + "2": "130.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1983", + "2": "127.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1984", + "2": "121.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1985", + "2": "115.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1986", + "2": "109.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1987", + "2": "105.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1988", + "2": "103.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "12", + "1": "1989", + "2": "96.5", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1990", + "2": "94.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1991", + "2": "91.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1992", + "2": "90.0", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1993", + "2": "89.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1994", + "2": "89.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1995", + "2": "90.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1996", + "2": "88.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1997", + "2": "89.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1998", + "2": "87.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "1999", + "2": "83.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "12", + "1": "2000", + "2": "79.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1970", + "2": "155.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1971", + "2": "163.5", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1972", + "2": "179.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1973", + "2": "201.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1974", + "2": "212.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1975", + "2": "223.0", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1976", + "2": "230.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1977", + "2": "229.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1978", + "2": "224.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1979", + "2": "214.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1980", + "2": "215.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1981", + "2": "209.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1982", + "2": "210.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1983", + "2": "201.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1984", + "2": "183.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1985", + "2": "182.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1986", + "2": "179.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1987", + "2": "171.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1988", + "2": "173.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "13", + "1": "1989", + "2": "171.600006103516", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1990", + "2": "182.5", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1991", + "2": "170.399993896484", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1992", + "2": "167.600006103516", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1993", + "2": "167.600006103516", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1994", + "2": "170.100006103516", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1995", + "2": "175.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1996", + "2": "179.0", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1997", + "2": "186.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1998", + "2": "171.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "1999", + "2": "165.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "13", + "1": "2000", + "2": "156.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1970", + "2": "115.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1971", + "2": "119.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1972", + "2": "125.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1973", + "2": "126.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1974", + "2": "129.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1975", + "2": "133.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1976", + "2": "139.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1977", + "2": "140.0", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1978", + "2": "142.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1979", + "2": "140.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1980", + "2": "143.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1981", + "2": "144.0", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1982", + "2": "143.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1983", + "2": "133.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1984", + "2": "128.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1985", + "2": "125.0", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1986", + "2": "121.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1987", + "2": "116.5", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1988", + "2": "110.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "14", + "1": "1989", + "2": "103.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1990", + "2": "101.5", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1991", + "2": "107.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1992", + "2": "108.5", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1993", + "2": "106.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1994", + "2": "105.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1995", + "2": "105.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1996", + "2": "106.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1997", + "2": "105.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1998", + "2": "103.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "1999", + "2": "101.0", + "3": "false", + "4": "true" + }, + { + "0": "14", + "1": "2000", + "2": "104.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1970", + "2": "128.5", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1971", + "2": "133.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1972", + "2": "136.5", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1973", + "2": "138.0", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1974", + "2": "142.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1975", + "2": "140.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1976", + "2": "144.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1977", + "2": "145.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1978", + "2": "143.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1979", + "2": "138.5", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1980", + "2": "141.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1981", + "2": "138.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1982", + "2": "139.5", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1983", + "2": "135.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1984", + "2": "135.5", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1985", + "2": "127.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1986", + "2": "119.0", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1987", + "2": "125.0", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1988", + "2": "125.0", + "3": "false", + "4": "false" + }, + { + "0": "15", + "1": "1989", + "2": "122.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1990", + "2": "117.5", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1991", + "2": "116.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1992", + "2": "114.5", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1993", + "2": "108.5", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1994", + "2": "101.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1995", + "2": "102.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1996", + "2": "100.0", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1997", + "2": "101.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1998", + "2": "94.5", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "1999", + "2": "85.5", + "3": "false", + "4": "true" + }, + { + "0": "15", + "1": "2000", + "2": "82.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1970", + "2": "104.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1971", + "2": "116.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1972", + "2": "96.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1973", + "2": "106.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1974", + "2": "110.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1975", + "2": "111.5", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1976", + "2": "116.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1977", + "2": "117.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1978", + "2": "118.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1979", + "2": "118.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1980", + "2": "117.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1981", + "2": "120.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1982", + "2": "119.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1983", + "2": "113.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1984", + "2": "110.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1985", + "2": "113.0", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1986", + "2": "104.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1987", + "2": "108.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1988", + "2": "94.0999984741211", + "3": "false", + "4": "false" + }, + { + "0": "16", + "1": "1989", + "2": "92.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1990", + "2": "90.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1991", + "2": "86.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1992", + "2": "83.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1993", + "2": "81.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1994", + "2": "83.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1995", + "2": "84.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1996", + "2": "81.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1997", + "2": "84.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1998", + "2": "83.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "1999", + "2": "80.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "16", + "1": "2000", + "2": "76.0", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1970", + "2": "93.4000015258789", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1971", + "2": "105.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1972", + "2": "112.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1973", + "2": "115.0", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1974", + "2": "117.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1975", + "2": "116.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1976", + "2": "120.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1977", + "2": "122.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1978", + "2": "124.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1979", + "2": "123.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1980", + "2": "127.0", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1981", + "2": "125.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1982", + "2": "125.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1983", + "2": "122.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1984", + "2": "116.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1985", + "2": "115.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1986", + "2": "113.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1987", + "2": "110.0", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1988", + "2": "109.0", + "3": "false", + "4": "false" + }, + { + "0": "17", + "1": "1989", + "2": "108.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1990", + "2": "101.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1991", + "2": "105.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1992", + "2": "103.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1993", + "2": "105.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1994", + "2": "106.0", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1995", + "2": "107.5", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1996", + "2": "106.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1997", + "2": "106.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1998", + "2": "107.0", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "1999", + "2": "103.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "17", + "1": "2000", + "2": "97.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1970", + "2": "121.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1971", + "2": "127.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1972", + "2": "130.0", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1973", + "2": "132.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1974", + "2": "135.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1975", + "2": "135.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1976", + "2": "139.5", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1977", + "2": "140.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1978", + "2": "141.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1979", + "2": "140.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1980", + "2": "142.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1981", + "2": "140.5", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1982", + "2": "139.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1983", + "2": "134.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1984", + "2": "130.0", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1985", + "2": "129.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1986", + "2": "128.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1987", + "2": "128.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1988", + "2": "127.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "18", + "1": "1989", + "2": "122.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1990", + "2": "119.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1991", + "2": "119.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1992", + "2": "122.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1993", + "2": "121.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1994", + "2": "119.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1995", + "2": "124.0", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1996", + "2": "124.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1997", + "2": "120.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1998", + "2": "120.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "1999", + "2": "118.0", + "3": "false", + "4": "true" + }, + { + "0": "18", + "1": "2000", + "2": "113.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1970", + "2": "111.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1971", + "2": "115.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1972", + "2": "122.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1973", + "2": "119.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1974", + "2": "121.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1975", + "2": "123.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1976", + "2": "124.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1977", + "2": "127.0", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1978", + "2": "127.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1979", + "2": "120.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1980", + "2": "122.0", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1981", + "2": "121.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1982", + "2": "122.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1983", + "2": "113.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1984", + "2": "110.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1985", + "2": "103.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1986", + "2": "97.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1987", + "2": "91.6999969482422", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1988", + "2": "87.0999984741211", + "3": "false", + "4": "false" + }, + { + "0": "19", + "1": "1989", + "2": "86.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1990", + "2": "84.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1991", + "2": "82.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1992", + "2": "86.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1993", + "2": "86.0", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1994", + "2": "88.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1995", + "2": "90.5", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1996", + "2": "87.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1997", + "2": "88.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1998", + "2": "89.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "1999", + "2": "82.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "19", + "1": "2000", + "2": "75.5", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1970", + "2": "108.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1971", + "2": "108.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1972", + "2": "104.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1973", + "2": "106.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1974", + "2": "110.5", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1975", + "2": "114.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1976", + "2": "118.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1977", + "2": "117.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1978", + "2": "117.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1979", + "2": "116.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1980", + "2": "116.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1981", + "2": "117.0", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1982", + "2": "117.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1983", + "2": "110.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1984", + "2": "107.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1985", + "2": "105.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1986", + "2": "103.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1987", + "2": "101.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1988", + "2": "92.9000015258789", + "3": "false", + "4": "false" + }, + { + "0": "20", + "1": "1989", + "2": "93.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1990", + "2": "89.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1991", + "2": "92.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1992", + "2": "90.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1993", + "2": "91.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1994", + "2": "85.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1995", + "2": "88.5", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1996", + "2": "86.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1997", + "2": "85.5", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1998", + "2": "83.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "1999", + "2": "86.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "20", + "1": "2000", + "2": "77.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1970", + "2": "189.5", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1971", + "2": "190.5", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1972", + "2": "198.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1973", + "2": "201.5", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1974", + "2": "204.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1975", + "2": "205.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1976", + "2": "201.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1977", + "2": "190.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1978", + "2": "187.0", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1979", + "2": "183.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1980", + "2": "177.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1981", + "2": "171.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1982", + "2": "165.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1983", + "2": "159.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1984", + "2": "136.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1985", + "2": "146.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1986", + "2": "142.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1987", + "2": "147.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1988", + "2": "141.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "21", + "1": "1989", + "2": "137.899993896484", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1990", + "2": "137.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1991", + "2": "115.5", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1992", + "2": "110.0", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1993", + "2": "108.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1994", + "2": "105.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1995", + "2": "100.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1996", + "2": "99.0", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1997", + "2": "95.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1998", + "2": "102.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "1999", + "2": "103.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "21", + "1": "2000", + "2": "93.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1970", + "2": "265.70001220703097", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1971", + "2": "278.0", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1972", + "2": "296.20001220703097", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1973", + "2": "279.0", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1974", + "2": "269.79998779296903", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1975", + "2": "269.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1976", + "2": "290.5", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1977", + "2": "278.79998779296903", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1978", + "2": "269.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1979", + "2": "254.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1980", + "2": "247.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1981", + "2": "245.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1982", + "2": "239.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1983", + "2": "232.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1984", + "2": "215.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1985", + "2": "201.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1986", + "2": "195.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1987", + "2": "195.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1988", + "2": "180.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "22", + "1": "1989", + "2": "172.899993896484", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1990", + "2": "152.399993896484", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1991", + "2": "144.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1992", + "2": "143.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1993", + "2": "148.899993896484", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1994", + "2": "153.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1995", + "2": "158.5", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1996", + "2": "158.0", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1997", + "2": "174.399993896484", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1998", + "2": "173.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "1999", + "2": "171.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "22", + "1": "2000", + "2": "147.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1970", + "2": "90.0", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1971", + "2": "92.5999984741211", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1972", + "2": "99.3000030517578", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1973", + "2": "98.9000015258789", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1974", + "2": "100.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1975", + "2": "103.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1976", + "2": "102.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1977", + "2": "102.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1978", + "2": "103.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1979", + "2": "101.0", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1980", + "2": "102.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1981", + "2": "103.0", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1982", + "2": "97.5", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1983", + "2": "96.3000030517578", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1984", + "2": "88.9000015258789", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1985", + "2": "88.0", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1986", + "2": "88.1999969482422", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1987", + "2": "82.3000030517578", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1988", + "2": "77.6999969482422", + "3": "false", + "4": "false" + }, + { + "0": "23", + "1": "1989", + "2": "74.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1990", + "2": "70.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1991", + "2": "69.90000152587889", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1992", + "2": "71.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1993", + "2": "69.0", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1994", + "2": "68.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1995", + "2": "67.0", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1996", + "2": "65.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1997", + "2": "61.7999992370605", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1998", + "2": "62.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "1999", + "2": "59.7000007629395", + "3": "false", + "4": "true" + }, + { + "0": "23", + "1": "2000", + "2": "53.7999992370605", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1970", + "2": "172.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1971", + "2": "187.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1972", + "2": "214.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1973", + "2": "226.5", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1974", + "2": "227.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1975", + "2": "226.0", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1976", + "2": "230.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1977", + "2": "217.0", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1978", + "2": "205.5", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1979", + "2": "197.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1980", + "2": "187.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1981", + "2": "179.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1982", + "2": "179.0", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1983", + "2": "169.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1984", + "2": "160.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1985", + "2": "156.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1986", + "2": "154.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1987", + "2": "150.5", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1988", + "2": "146.0", + "3": "false", + "4": "false" + }, + { + "0": "24", + "1": "1989", + "2": "139.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1990", + "2": "133.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1991", + "2": "132.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1992", + "2": "128.899993896484", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1993", + "2": "129.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1994", + "2": "112.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1995", + "2": "124.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1996", + "2": "129.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1997", + "2": "125.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1998", + "2": "126.0", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "1999", + "2": "113.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "24", + "1": "2000", + "2": "109.0", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1970", + "2": "93.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1971", + "2": "98.5", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1972", + "2": "103.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1973", + "2": "108.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1974", + "2": "110.5", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1975", + "2": "117.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1976", + "2": "125.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1977", + "2": "122.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1978", + "2": "121.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1979", + "2": "121.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1980", + "2": "123.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1981", + "2": "125.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1982", + "2": "126.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1983", + "2": "119.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1984", + "2": "109.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1985", + "2": "103.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1986", + "2": "99.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1987", + "2": "92.3000030517578", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1988", + "2": "87.0999984741211", + "3": "false", + "4": "false" + }, + { + "0": "25", + "1": "1989", + "2": "84.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1990", + "2": "77.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1991", + "2": "85.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1992", + "2": "74.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1993", + "2": "83.0", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1994", + "2": "81.0", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1995", + "2": "80.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1996", + "2": "80.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1997", + "2": "77.5", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1998", + "2": "79.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "1999", + "2": "74.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "25", + "1": "2000", + "2": "72.5", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1970", + "2": "121.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1971", + "2": "124.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1972", + "2": "124.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1973", + "2": "120.5", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1974", + "2": "122.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1975", + "2": "122.5", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1976", + "2": "124.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1977", + "2": "127.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1978", + "2": "131.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1979", + "2": "130.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1980", + "2": "133.5", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1981", + "2": "132.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1982", + "2": "134.0", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1983", + "2": "130.0", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1984", + "2": "127.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1985", + "2": "126.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1986", + "2": "126.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1987", + "2": "124.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1988", + "2": "122.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "26", + "1": "1989", + "2": "118.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1990", + "2": "115.5", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1991", + "2": "113.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1992", + "2": "112.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1993", + "2": "108.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1994", + "2": "108.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1995", + "2": "111.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1996", + "2": "107.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1997", + "2": "108.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1998", + "2": "106.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "1999", + "2": "104.0", + "3": "false", + "4": "true" + }, + { + "0": "26", + "1": "2000", + "2": "99.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1970", + "2": "108.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1971", + "2": "115.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1972", + "2": "121.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1973", + "2": "124.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1974", + "2": "130.5", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1975", + "2": "132.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1976", + "2": "138.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1977", + "2": "140.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1978", + "2": "143.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1979", + "2": "141.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1980", + "2": "141.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1981", + "2": "143.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1982", + "2": "147.0", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1983", + "2": "140.0", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1984", + "2": "128.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1985", + "2": "124.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1986", + "2": "119.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1987", + "2": "113.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1988", + "2": "103.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "27", + "1": "1989", + "2": "97.5", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1990", + "2": "88.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1991", + "2": "87.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1992", + "2": "86.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1993", + "2": "86.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1994", + "2": "104.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1995", + "2": "109.5", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1996", + "2": "110.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1997", + "2": "111.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1998", + "2": "112.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "1999", + "2": "111.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "27", + "1": "2000", + "2": "108.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1970", + "2": "107.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1971", + "2": "106.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1972", + "2": "109.0", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1973", + "2": "110.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1974", + "2": "114.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1975", + "2": "114.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1976", + "2": "118.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1977", + "2": "120.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1978", + "2": "122.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1979", + "2": "122.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1980", + "2": "124.0", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1981", + "2": "125.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1982", + "2": "123.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1983", + "2": "125.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1984", + "2": "115.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1985", + "2": "115.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1986", + "2": "113.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1987", + "2": "110.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1988", + "2": "107.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "28", + "1": "1989", + "2": "107.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1990", + "2": "101.300003051758", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1991", + "2": "102.5", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1992", + "2": "96.1999969482422", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1993", + "2": "94.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1994", + "2": "95.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1995", + "2": "95.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1996", + "2": "93.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1997", + "2": "92.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1998", + "2": "92.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "1999", + "2": "91.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "28", + "1": "2000", + "2": "87.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1970", + "2": "123.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1971", + "2": "123.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1972", + "2": "134.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1973", + "2": "142.0", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1974", + "2": "146.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1975", + "2": "154.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1976", + "2": "150.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1977", + "2": "148.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1978", + "2": "146.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1979", + "2": "145.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1980", + "2": "149.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1981", + "2": "151.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1982", + "2": "146.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1983", + "2": "135.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1984", + "2": "136.899993896484", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1985", + "2": "133.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1986", + "2": "136.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1987", + "2": "124.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1988", + "2": "138.0", + "3": "false", + "4": "false" + }, + { + "0": "29", + "1": "1989", + "2": "120.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1990", + "2": "101.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1991", + "2": "103.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1992", + "2": "100.099998474121", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1993", + "2": "94.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1994", + "2": "91.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1995", + "2": "90.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1996", + "2": "87.5", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1997", + "2": "90.0", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1998", + "2": "88.6999969482422", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "1999", + "2": "86.9000015258789", + "3": "false", + "4": "true" + }, + { + "0": "29", + "1": "2000", + "2": "83.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1970", + "2": "103.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1971", + "2": "115.0", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1972", + "2": "118.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1973", + "2": "125.5", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1974", + "2": "129.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1975", + "2": "130.5", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1976", + "2": "136.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1977", + "2": "137.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1978", + "2": "140.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1979", + "2": "135.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1980", + "2": "138.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1981", + "2": "136.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1982", + "2": "136.0", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1983", + "2": "131.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1984", + "2": "127.0", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1985", + "2": "125.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1986", + "2": "126.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1987", + "2": "126.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1988", + "2": "124.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "30", + "1": "1989", + "2": "122.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1990", + "2": "118.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1991", + "2": "121.5", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1992", + "2": "112.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1993", + "2": "115.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1994", + "2": "112.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1995", + "2": "109.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1996", + "2": "102.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1997", + "2": "124.5", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1998", + "2": "126.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "1999", + "2": "109.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "30", + "1": "2000", + "2": "103.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1970", + "2": "92.6999969482422", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1971", + "2": "96.6999969482422", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1972", + "2": "103.0", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1973", + "2": "103.5", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1974", + "2": "108.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1975", + "2": "113.5", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1976", + "2": "116.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1977", + "2": "115.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1978", + "2": "116.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1979", + "2": "117.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1980", + "2": "114.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1981", + "2": "115.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1982", + "2": "113.0", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1983", + "2": "109.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1984", + "2": "105.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1985", + "2": "104.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1986", + "2": "97.0", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1987", + "2": "95.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1988", + "2": "91.9000015258789", + "3": "false", + "4": "false" + }, + { + "0": "31", + "1": "1989", + "2": "87.4000015258789", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1990", + "2": "88.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1991", + "2": "91.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1992", + "2": "93.0", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1993", + "2": "91.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1994", + "2": "94.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1995", + "2": "98.5999984741211", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1996", + "2": "92.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1997", + "2": "88.8000030517578", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1998", + "2": "88.3000030517578", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "1999", + "2": "83.5", + "3": "false", + "4": "true" + }, + { + "0": "31", + "1": "2000", + "2": "75.0999984741211", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1970", + "2": "99.8000030517578", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1971", + "2": "106.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1972", + "2": "111.5", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1973", + "2": "109.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1974", + "2": "114.800003051758", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1975", + "2": "117.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1976", + "2": "121.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1977", + "2": "124.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1978", + "2": "127.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1979", + "2": "127.199996948242", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1980", + "2": "130.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1981", + "2": "129.100006103516", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1982", + "2": "131.399993896484", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1983", + "2": "129.0", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1984", + "2": "125.099998474121", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1985", + "2": "128.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1986", + "2": "129.0", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1987", + "2": "130.600006103516", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1988", + "2": "125.300003051758", + "3": "false", + "4": "false" + }, + { + "0": "32", + "1": "1989", + "2": "124.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1990", + "2": "121.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1991", + "2": "120.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1992", + "2": "121.0", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1993", + "2": "120.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1994", + "2": "118.800003051758", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1995", + "2": "125.400001525879", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1996", + "2": "119.199996948242", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1997", + "2": "118.900001525879", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1998", + "2": "119.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "1999", + "2": "115.599998474121", + "3": "false", + "4": "true" + }, + { + "0": "32", + "1": "2000", + "2": "108.699996948242", + "3": "false", + "4": "true" + }, + { + "0": "33", + "1": "1970", + "2": "106.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1971", + "2": "108.900001525879", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1972", + "2": "108.599998474121", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1973", + "2": "110.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1974", + "2": "114.699996948242", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1975", + "2": "116.0", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1976", + "2": "121.400001525879", + "3": "false", + "4": "false" + }, + { + "0": "33", + "1": "1977", + "2": "124.199996948242", + "3": "false", + "4": "false" + } + ], + "schema": [ + { + "key": "0", + "name": "state", + "type": "int" + }, + { + "key": "1", + "name": "year", + "type": "int" + }, + { + "key": "2", + "name": "cigsale", + "type": "double" + }, + { + "key": "3", + "name": "california", + "type": "boolean" + }, + { + "key": "4", + "name": "after_treatment", + "type": "boolean" + } + ], + "truncated": false + } + }, + "type": "Synapse.DataFrame" + } + }, + "version": "0.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 1a2e3206e52007d6d41d1f2633a90bdb4abb32c4 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 25 Oct 2023 22:42:34 -0700 Subject: [PATCH 18/41] fixing indexing logic --- .../ml/causal/SyntheticControlEstimator.scala | 9 ++- .../causal/SyntheticDiffInDiffEstimator.scala | 14 ++-- .../ml/causal/SyntheticEstimator.scala | 77 +++++++++---------- .../VerifySyntheticDiffInDiffEstimator.scala | 11 +-- .../ml/causal/VerifySyntheticEstimator.scala | 4 +- 5 files changed, 57 insertions(+), 58 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 746dc9bafb..9226e14afe 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -28,9 +28,9 @@ class SyntheticControlEstimator(override val uid: String) .toDF val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache - val controlPreDf = controlDf.filter(not(postTreatment)).cache - val timeIdx = createIndex(controlPreDf, getTimeCol, TimeIdxCol).cache - val unitIdx = createIndex(controlPreDf, getUnitCol, UnitIdxCol).cache + val timeIdx = createIndex(preDf, getTimeCol, TimeIdxCol).cache + val unitIdx = createIndex(controlDf, getUnitCol, UnitIdxCol).cache + val size = (timeIdx.count, unitIdx.count) // indexing val indexedPreDf = preDf.join(timeIdx, preDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") @@ -41,7 +41,8 @@ class SyntheticControlEstimator(override val uid: String) val (unitWeights, unitIntercept, lossHistory) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta = 0d, - fitIntercept = false + fitIntercept = false, + size ) // join weights diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 06d1561cd8..cb99cff797 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -27,9 +27,11 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .toDF val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache - val controlPreDf = controlDf.filter(not(postTreatment)).cache - val timeIdx = createIndex(controlPreDf, getTimeCol, TimeIdxCol).cache - val unitIdx = createIndex(controlPreDf, getUnitCol, UnitIdxCol).cache + val timeIdx = createIndex(preDf, getTimeCol, TimeIdxCol).cache + timeIdx.show(100, false) + val unitIdx = createIndex(controlDf, getUnitCol, UnitIdxCol).cache + unitIdx.show(100, false) + val size = (unitIdx.count, timeIdx.count) // indexing val indexedControlDf = controlDf.join(timeIdx, controlDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") @@ -43,8 +45,9 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .localCheckpoint(true) // fit time weights + val (timeWeights, timeIntercept, lossHistoryTimeWeights) = fitTimeWeights( - handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt) + handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), size ) // fit unit weights @@ -52,7 +55,8 @@ class SyntheticDiffInDiffEstimator(override val uid: String) val (unitWeights, unitIntercept, lossHistoryUnitWeights) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta, - fitIntercept = true + fitIntercept = true, + size.swap ) // join weights diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index d32487629b..096b366f24 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -25,8 +25,7 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] val weightsCol = "weights" private[causal] val epsilon = 1E-10 - private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean): (DVector, Double, Seq[Double]) = { - val size = matrixOps.size(A) + private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, size: (Long, Long)): (DVector, Double, Seq[Double]) = { if (size._1 * size._2 <= getLocalSolverThreshold) { // If matrix size is less than LocalSolverThreshold (defaults to 1M), // collect the data on the driver node and solve it locally, where matrix-vector @@ -35,8 +34,8 @@ trait SyntheticEstimator extends SynapseMLLogging { implicit val bzVectorOps: VectorOps[BDV[Double]] = BzVectorOps implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps - val bzA = convertToBDM(A.collect()) - val bzb = convertToBDV(b.collect()) // b is assumed to be non-sparse + val bzA = convertToBDM(A.collect(), size) + val bzb = convertToBDV(b.collect(), size._1) val solver = new ConstrainedLeastSquare[BDM[Double], BDV[Double]]( step = this.getStepSize, maxIter = this.getMaxIter, numIterNoChange = get(numIterNoChange), tol = this.getTol @@ -56,19 +55,20 @@ trait SyntheticEstimator extends SynapseMLLogging { } } - private[causal] def fitTimeWeights(indexedControlDf: DataFrame): (DVector, Double, Seq[Double]) = logVerb("fitTimeWeights", { - val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache + private[causal] def fitTimeWeights(indexedControlDf: DataFrame, size: (Long, Long)): (DVector, Double, Seq[Double]) = + logVerb("fitTimeWeights", { + val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache - val outcomePre = indexedPreControl - .toDMatrix(UnitIdxCol, TimeIdxCol, getOutcomeCol) + val outcomePre = indexedPreControl + .toDMatrix(UnitIdxCol, TimeIdxCol, getOutcomeCol) - val outcomePostMean = indexedControlDf.filter(postTreatment) - .groupBy(col(UnitIdxCol).as("i")) - .agg(avg(col(getOutcomeCol)).as("value")) - .as[VectorEntry] + val outcomePostMean = indexedControlDf.filter(postTreatment) + .groupBy(col(UnitIdxCol).as("i")) + .agg(avg(col(getOutcomeCol)).as("value")) + .as[VectorEntry] - solveCLS(outcomePre, outcomePostMean, lambda = 0d, fitIntercept = true) - }) + solveCLS(outcomePre, outcomePostMean, lambda = 0d, fitIntercept = true, size) + }) private[causal] def calculateRegularization(data: DataFrame): Double = logVerb("calculateRegularization", { val Row(firstDiffStd: Double) = data @@ -88,25 +88,25 @@ trait SyntheticEstimator extends SynapseMLLogging { }) private[causal] def fitUnitWeights(indexedPreDf: DataFrame, - zeta: Double, - fitIntercept: Boolean): (DVector, Double, Seq[Double]) = logVerb("fitUnitWeights", { - - - val outcomePreControl = indexedPreDf.filter(not(treatment)) - .toDMatrix(TimeIdxCol, UnitIdxCol, getOutcomeCol) - - val outcomePreTreatMean = indexedPreDf.filter(treatment) - .groupBy(col(TimeIdxCol).as("i")) - .agg(avg(outcome).as("value")) - .as[VectorEntry] - - val lambda = if (zeta == 0) 0d else { - val t_pre = matrixOps.size(outcomePreControl)._1 // # of time periods pre-treatment - zeta * zeta * t_pre - } + zeta: Double, + fitIntercept: Boolean, + size: (Long, Long)): (DVector, Double, Seq[Double]) = + logVerb("fitUnitWeights", { + val outcomePreControl = indexedPreDf.filter(not(treatment)) + .toDMatrix(TimeIdxCol, UnitIdxCol, getOutcomeCol) + + val outcomePreTreatMean = indexedPreDf.filter(treatment) + .groupBy(col(TimeIdxCol).as("i")) + .agg(avg(outcome).as("value")) + .as[VectorEntry] + + val lambda = if (zeta == 0) 0d else { + val t_pre = matrixOps.size(outcomePreControl)._1 // # of time periods pre-treatment + zeta * zeta * t_pre + } - solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept) - }) + solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept, size) + }) private[causal] def handleMissingOutcomes(indexed: DataFrame, maxTimeLength: Int): DataFrame = { // "skip", "zero", "impute" @@ -189,10 +189,9 @@ object SyntheticEstimator { } } - private[causal] def convertToBDM(mat: Array[MatrixEntry]): BDM[Double] = { - val numRows = mat.map(_.i).max.toInt + 1 - val numCols = mat.map(_.j).max.toInt + 1 - val denseMatrix = BDM.zeros[Double](numRows, numCols) + private[causal] def convertToBDM(mat: Array[MatrixEntry], size: (Long, Long)): BDM[Double] = { + + val denseMatrix = BDM.zeros[Double](size._1.toInt, size._2.toInt) mat.foreach(entry => { denseMatrix(entry.i.toInt, entry.j.toInt) = entry.value }) @@ -200,10 +199,8 @@ object SyntheticEstimator { denseMatrix } - private[causal] def convertToBDV(vec: Array[VectorEntry]): BDV[Double] = { - // assuming vec is not sparse. - val length = vec.map(_.i).max.toInt + 1 - val denseVector = BDV.zeros[Double](length) + private[causal] def convertToBDV(vec: Array[VectorEntry], size: Long): BDV[Double] = { + val denseVector = BDV.zeros[Double](size.toInt) vec.foreach(entry => { denseVector(entry.i.toInt) = entry.value diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala index e059dac6f8..bd84c32002 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala @@ -21,15 +21,12 @@ class VerifySyntheticDiffInDiffEstimator private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) private lazy val data = { - - // 101 - 110 val rand1 = rb.gaussian(100, 1) val controlPre1 = for { unit <- 1 to 99 time <- 1 to 10 } yield (unit, time, 0, 0, rand1.sample + time) - // 51 - 60 val rand2 = rb.gaussian(50, 1) val controlPre2 = for { unit <- 100 to 100 @@ -80,21 +77,21 @@ class VerifySyntheticDiffInDiffEstimator implicit val VectorOps: VectorOps[DVector] = DVectorOps val summary = estimator.fit(df).getSummary - assert(summary.timeIntercept.get === 4.945419871092341) + assert(summary.timeIntercept.get === 4.948917186627611) val timeWeights = summary.timeWeights.get.toBreeze assert(sum(timeWeights) === 1.0) assert(timeWeights.size === 10) assert(timeWeights.forall(0 <= _ && _ <= 1)) - assert(summary.unitIntercept.get === -54.712303815108314) + assert(summary.unitIntercept.get === -54.625356763024584) val unitWeights = summary.unitWeights.get.toBreeze assert(sum(unitWeights) === 1.0) assert(unitWeights.size === 100) assert(unitWeights.forall(0 <= _ && _ <= 1)) - assert(summary.treatmentEffect === -14.92249952070377) - assert(summary.standardError === 0.28948753364970986) + assert(summary.treatmentEffect === -14.934064851225985) + assert(summary.standardError === 0.30221430259614196) } override def testObjects(): Seq[TestObject[SyntheticDiffInDiffEstimator]] = Seq( diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala index ef3bc45ae7..e8f9fb74be 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala @@ -56,7 +56,7 @@ class VerifySyntheticEstimator extends TestBase { MatrixEntry(0, 0, 3.0), MatrixEntry(1, 1, 3.0), MatrixEntry(2, 2, 3.0) - )) + ), (3, 3)) assert( bdm === (BDM.eye[Double](3) *:* 3d) @@ -68,7 +68,7 @@ class VerifySyntheticEstimator extends TestBase { VectorEntry(0, 3.0), VectorEntry(1, 3.0), VectorEntry(2, 3.0) - )) + ), 3) assert( bdv === (BDV.ones[Double](3) *:* 3d) From 03b8bbc36ba555a743a33f150d582b0e1baa15a2 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 15:42:49 -0700 Subject: [PATCH 19/41] add file headers Signed-off-by: Jason Wang --- .../synapse/ml/causal/BaseDiffInDiffEstimator.scala | 10 ++++++++-- .../microsoft/azure/synapse/ml/causal/CacheOps.scala | 3 +++ .../azure/synapse/ml/causal/DiffInDiffEstimator.scala | 11 +++++++++-- .../synapse/ml/causal/DiffInDiffEstimatorParams.scala | 5 ++++- .../azure/synapse/ml/causal/SharedParams.scala | 3 +++ .../synapse/ml/causal/SyntheticControlEstimator.scala | 3 +++ .../ml/causal/SyntheticDiffInDiffEstimator.scala | 5 ++++- .../azure/synapse/ml/causal/SyntheticEstimator.scala | 5 ++++- .../synapse/ml/causal/SyntheticEstimatorParams.scala | 3 +++ .../azure/synapse/ml/causal/linalg/MatrixOps.scala | 5 ++++- .../azure/synapse/ml/causal/linalg/VectorOps.scala | 3 +++ .../azure/synapse/ml/causal/linalg/package.scala | 3 +++ .../ml/causal/opt/ConstrainedLeastSquare.scala | 7 ++++++- .../azure/synapse/ml/causal/opt/MirrorDescent.scala | 5 ++++- 14 files changed, 61 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 84e6a09c55..4edc6efa73 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.DVector @@ -41,7 +44,10 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) private[causal] val interactionCol = "interaction" - private[causal] def fitLinearModel(df: DataFrame, featureCols: Array[String], fitIntercept: Boolean, weightCol: Option[String] = None) = { + private[causal] def fitLinearModel(df: DataFrame, + featureCols: Array[String], + fitIntercept: Boolean, + weightCol: Option[String] = None) = { val assembler = new VectorAssembler() .setInputCols(featureCols) .setOutputCol("features") @@ -151,4 +157,4 @@ class DiffInDiffModel(override val uid: String) } } -object DiffInDiffModel extends ComplexParamsReadable[DiffInDiffModel] \ No newline at end of file +object DiffInDiffModel extends ComplexParamsReadable[DiffInDiffModel] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala index 662689860a..886db93588 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/CacheOps.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import breeze.linalg.{DenseVector => BDV} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index 4665e533a1..20952db9d8 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable @@ -30,7 +33,11 @@ class DiffInDiffEstimator(override val uid: String) ) .withColumn(interactionCol, treatment * postTreatment) - val linearModel = fitLinearModel(didData, Array(getPostTreatmentCol, getTreatmentCol, interactionCol), fitIntercept = true) + val linearModel = fitLinearModel( + didData, + Array(getPostTreatmentCol, getTreatmentCol, interactionCol), + fitIntercept = true + ) val treatmentEffect = linearModel.coefficients(2) val standardError = linearModel.summary.coefficientStandardErrors(2) @@ -42,4 +49,4 @@ class DiffInDiffEstimator(override val uid: String) }, dataset.columns.length) } -object DiffInDiffEstimator extends ComplexParamsReadable[DiffInDiffEstimator] \ No newline at end of file +object DiffInDiffEstimator extends ComplexParamsReadable[DiffInDiffEstimator] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala index 5756fdf061..a26f6128c1 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import org.apache.spark.ml.param.Params @@ -5,4 +8,4 @@ import org.apache.spark.ml.param.Params trait DiffInDiffEstimatorParams extends Params with HasTreatmentCol with HasOutcomeCol - with HasPostTreatmentCol \ No newline at end of file + with HasPostTreatmentCol diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala index 8baff45c40..bed393e2de 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import org.apache.spark.ml.param.{Param, Params} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 9226e14afe..f2dbee0351 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index cb99cff797..004f00085b 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable @@ -118,4 +121,4 @@ class SyntheticDiffInDiffEstimator(override val uid: String) }, dataset.columns.length) } -object SyntheticDiffInDiffEstimator extends ComplexParamsReadable[SyntheticDiffInDiffEstimator] \ No newline at end of file +object SyntheticDiffInDiffEstimator extends ComplexParamsReadable[SyntheticDiffInDiffEstimator] diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 096b366f24..6884d6ff10 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import org.apache.spark.sql.expressions.{UserDefinedFunction, Window} @@ -227,4 +230,4 @@ object SyntheticEstimator { // the regularization term will be incorrect. assignRowIndex(data.select(col(inputCol)).distinct.orderBy(col(inputCol)), indexCol) } -} \ No newline at end of file +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala index fb756dbb09..13a16df35c 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import org.apache.spark.ml.param.{IntParam, LongParam, Param, ParamValidators, Params} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala index 42d156075c..8fbdd280fe 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/MatrixOps.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} @@ -133,4 +136,4 @@ object BzMatrixOps extends MatrixOps[BDM[Double], BDV[Double]] { } override def size(matrix: BDM[Double]): (Long, Long) = (matrix.rows, matrix.cols) -} \ No newline at end of file +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala index 6cf9602bb0..adb546b531 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{norm, DenseVector => BDV, max => bmax, sum => bsum} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala index 33a303c7d5..495c479826 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/package.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV, SparseVector => BSV} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala index 39393a6f1b..3e15831436 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -1,9 +1,13 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.opt import breeze.optimize.DiffFunction import com.microsoft.azure.synapse.ml.causal.CacheOps import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixOps, VectorOps} +// scalastyle:off non.ascii.character.disallowed /** * Solver for the following constrained least square problem: * minimize ||Ax-b||^2^ + λ||x||^2^, s.t. 1^T^x = 1, 0 ≤ x ≤ 1 @@ -12,6 +16,7 @@ import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixOps, VectorOps} * @param numIterNoChange max number of iteration without change in loss function allowed before termination. * @param tol tolerance for loss function */ +// scalastyle:on private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, maxIter: Int, numIterNoChange: Option[Int] = None, @@ -75,4 +80,4 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, (x, 0d, md.history.map(_.valueAt)) } } -} \ No newline at end of file +} diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala index bfa4bccf9f..0af8d4536e 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/MirrorDescent.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.opt import breeze.optimize.DiffFunction @@ -133,4 +136,4 @@ private[opt] class MirrorDescent[TVec](private val func: DiffFunction[TVec], logger.debug("Elapsed time: " + (t1 - t0) / 1E9 + "s") result } -} \ No newline at end of file +} From 5787e9e854dac3cf72361922c2ffe91277f0ab7c Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 16:34:24 -0700 Subject: [PATCH 20/41] Add feature name to logClass call --- .../azure/synapse/ml/causal/DiffInDiffEstimator.scala | 4 ++-- .../azure/synapse/ml/causal/SyntheticControlEstimator.scala | 3 ++- .../synapse/ml/causal/SyntheticDiffInDiffEstimator.scala | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index 20952db9d8..086e394c1e 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -4,7 +4,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable -import com.microsoft.azure.synapse.ml.logging.SynapseMLLogging +import com.microsoft.azure.synapse.ml.logging.{FeatureNames, SynapseMLLogging} import org.apache.spark.ml.util.Identifiable import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.sql._ @@ -17,7 +17,7 @@ class DiffInDiffEstimator(override val uid: String) with Wrappable with SynapseMLLogging { - logClass() + logClass(FeatureNames.Causal) def this() = this(Identifiable.randomUID("did")) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index f2dbee0351..d4c59d69c8 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -4,6 +4,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.logging.FeatureNames import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer} import org.apache.spark.ml.util.Identifiable import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable, Pipeline} @@ -18,7 +19,7 @@ class SyntheticControlEstimator(override val uid: String) with ComplexParamsWritable with Wrappable { - logClass() + logClass(FeatureNames.Causal) import SyntheticEstimator._ diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 004f00085b..4740caefc3 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -4,6 +4,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.logging.FeatureNames import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.functions._ @@ -17,7 +18,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) with ComplexParamsWritable with Wrappable { - logClass() + logClass(FeatureNames.Causal) import SyntheticEstimator._ From faa190024ceb81393b2aa002fea4863beb374a64 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 16:59:27 -0700 Subject: [PATCH 21/41] more scalastyle fixes --- .../azure/synapse/ml/causal/SyntheticControlEstimator.scala | 1 + .../synapse/ml/causal/SyntheticDiffInDiffEstimator.scala | 6 ++---- .../azure/synapse/ml/causal/SyntheticEstimator.scala | 3 ++- .../azure/synapse/ml/causal/linalg/VectorOps.scala | 4 +++- .../synapse/ml/causal/opt/ConstrainedLeastSquare.scala | 3 +-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index d4c59d69c8..cb5d0c7c3d 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -25,6 +25,7 @@ class SyntheticControlEstimator(override val uid: String) def this() = this(Identifiable.randomUID("syncon")) + // scalastyle:off method.length override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ val df = dataset .withColumn(getTreatmentCol, treatment.cast(BooleanType)) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 4740caefc3..34210481a3 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -24,6 +24,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) def this() = this(Identifiable.randomUID("syndid")) + // scalastyle:off method.length override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ val df = dataset .withColumn(getTreatmentCol, treatment.cast(BooleanType)) @@ -32,9 +33,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache val timeIdx = createIndex(preDf, getTimeCol, TimeIdxCol).cache - timeIdx.show(100, false) val unitIdx = createIndex(controlDf, getUnitCol, UnitIdxCol).cache - unitIdx.show(100, false) val size = (unitIdx.count, timeIdx.count) // indexing @@ -49,7 +48,6 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .localCheckpoint(true) // fit time weights - val (timeWeights, timeIntercept, lossHistoryTimeWeights) = fitTimeWeights( handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), size ) @@ -66,7 +64,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) // join weights val Row(t: Long, u: Long) = df.agg( countDistinct(when(postTreatment, col(getTimeCol))), - countDistinct(when(treatment, col(getUnitCol))), + countDistinct(when(treatment, col(getUnitCol))) ).head val indexedDf = df.join(timeIdx, df(getTimeCol) === timeIdx(getTimeCol), "left_outer") diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 6884d6ff10..96b92ea08c 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -28,7 +28,8 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] val weightsCol = "weights" private[causal] val epsilon = 1E-10 - private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, size: (Long, Long)): (DVector, Double, Seq[Double]) = { + private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, size: (Long, Long)) + : (DVector, Double, Seq[Double]) = { if (size._1 * size._2 <= getLocalSolverThreshold) { // If matrix size is less than LocalSolverThreshold (defaults to 1M), // collect the data on the driver node and solve it locally, where matrix-vector diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala index adb546b531..db28494895 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/linalg/VectorOps.scala @@ -6,7 +6,9 @@ package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{norm, DenseVector => BDV, max => bmax, sum => bsum} import breeze.numerics.{abs => babs, exp => bexp} import breeze.stats.{mean => bmean} -import org.apache.spark.sql.functions.{coalesce, col, lit, abs => sabs, exp => sexp, max => smax, mean => smean, sum => ssum} +import org.apache.spark.sql.functions.{ + coalesce, col, lit, abs => sabs, exp => sexp, max => smax, mean => smean, sum => ssum +} import org.apache.spark.sql.{Encoder, Encoders, Row, SparkSession} case class VectorEntry(i: Long, value: Double) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala index 3e15831436..7ceb8e9a78 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -3,11 +3,11 @@ package com.microsoft.azure.synapse.ml.causal.opt +// scalastyle:off non.ascii.character.disallowed import breeze.optimize.DiffFunction import com.microsoft.azure.synapse.ml.causal.CacheOps import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixOps, VectorOps} -// scalastyle:off non.ascii.character.disallowed /** * Solver for the following constrained least square problem: * minimize ||Ax-b||^2^ + λ||x||^2^, s.t. 1^T^x = 1, 0 ≤ x ≤ 1 @@ -16,7 +16,6 @@ import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixOps, VectorOps} * @param numIterNoChange max number of iteration without change in loss function allowed before termination. * @param tol tolerance for loss function */ -// scalastyle:on private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, maxIter: Int, numIterNoChange: Option[Int] = None, From a7b6f20fe0368c0c68df38580cc74f00ddc54fd9 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 21:18:04 -0700 Subject: [PATCH 22/41] More scalastyle and unit test fixes --- .../python/synapse/ml/causal/DiffInDiffModel.py | 10 +++++++--- .../ml/causal/BaseDiffInDiffEstimator.scala | 6 +++++- .../ml/causal/VerifyDiffInDiffEstimator.scala | 3 +++ .../VerifySyntheticControlEstimator.scala | 10 +++++++--- .../VerifySyntheticDiffInDiffEstimator.scala | 9 ++++++--- .../ml/causal/VerifySyntheticEstimator.scala | 3 +++ .../ml/causal/linalg/VerifyMatrixOps.scala | 13 ++++++++----- .../ml/causal/linalg/VerifyVectorOps.scala | 17 ++++++++++------- .../opt/VerifyConstrainedLeastSquare.scala | 5 ++++- .../ml/causal/opt/VerifyMirrorDescent.scala | 5 ++++- .../ml/core/test/fuzzing/FuzzingTest.scala | 3 +++ 11 files changed, 60 insertions(+), 24 deletions(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 3e768d2e5e..7e16809c5f 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -9,7 +9,7 @@ from synapse.ml.causal._DiffInDiffModel import _DiffInDiffModel from pyspark.ml.common import inherit_doc from pyspark.sql import SparkSession, DataFrame -import numpy as np +from pyspark import SparkContext, SQLContext @inherit_doc class DiffInDiffModel(_DiffInDiffModel): @@ -24,12 +24,16 @@ def _unwrapOption(option): def __init__(self, java_obj=None) -> None: super(DiffInDiffModel, self).__init__(java_obj = java_obj) + + ctx = SparkContext._active_spark_context + sql_ctx = SQLContext.getOrCreate(ctx) + self.summary = java_obj.getSummary() self.treatmentEffect = self.summary.treatmentEffect() self.standardError = self.summary.standardError() self.timeIntercept = DiffInDiffModel._unwrapOption(self.summary.timeIntercept()) self.unitIntercept = DiffInDiffModel._unwrapOption(self.summary.unitIntercept()) - self.timeWeights = DiffInDiffModel._mapOption(java_obj.getTimeWeights(), lambda x: DataFrame(x, SparkSession.getActiveSession())) - self.unitWeights = DiffInDiffModel._mapOption(java_obj.getUnitWeights(), lambda x: DataFrame(x, SparkSession.getActiveSession())) + self.timeWeights = DiffInDiffModel._mapOption(java_obj.getTimeWeights(), lambda x: DataFrame(x, sql_ctx)) + self.unitWeights = DiffInDiffModel._mapOption(java_obj.getUnitWeights(), lambda x: DataFrame(x, sql_ctx)) self.lossHistoryTimeWeights = DiffInDiffModel._unwrapOption(self.summary.getLossHistoryTimeWeightsJava()) self.lossHistoryUnitWeights = DiffInDiffModel._unwrapOption(self.summary.getLossHistoryUnitWeightsJava()) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 4edc6efa73..c263cbf3d5 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -5,6 +5,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.DVector import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.logging.{FeatureNames, SynapseMLLogging} import com.microsoft.azure.synapse.ml.param.DataFrameParam import org.apache.spark.SparkException import org.apache.spark.ml.feature.VectorAssembler @@ -90,7 +91,10 @@ class DiffInDiffModel(override val uid: String) with HasUnitCol with HasTimeCol with Wrappable - with ComplexParamsWritable { + with ComplexParamsWritable + with SynapseMLLogging { + + logClass(FeatureNames.Causal) final val timeIndex = new DataFrameParam(this, "timeIndex", "time index") def getTimeIndex: DataFrame = $(timeIndex) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala index 739180f412..7729a3b941 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifyDiffInDiffEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import breeze.stats.distributions.RandBasis diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala index 066a9f2c86..650c2d3dff 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticControlEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import breeze.linalg.sum @@ -17,7 +20,7 @@ class VerifySyntheticControlEstimator Logger.getLogger("akka").setLevel(Level.WARN) private lazy val rb = RandBasis.withSeed(47) - private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + private implicit val equalityDouble: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) private lazy val data = { val rand1 = rb.gaussian(100, 2) @@ -77,11 +80,12 @@ class VerifySyntheticControlEstimator assert(summary.unitIntercept.get === 0d) - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val unitWeights = summary.unitWeights.get.toBreeze assert(sum(unitWeights) === 1d) assert(unitWeights.size === 100) - // Almost all weights go to the last unit since all other units are intentionally placed far away from treatment units. + // Almost all weights go to the last unit + // since all other units are intentionally placed far away from treatment units. assert(math.abs(unitWeights(99) - 1.0) < 0.01) assert(summary.treatmentEffect === 10.402769542126478) assert(summary.standardError === 0.1808473095549071) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala index bd84c32002..415baeee1d 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import breeze.linalg.sum @@ -18,7 +21,7 @@ class VerifySyntheticDiffInDiffEstimator Logger.getLogger("akka").setLevel(Level.WARN) private lazy val rb = RandBasis.withSeed(47) - private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + private implicit val equalityDouble: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) private lazy val data = { val rand1 = rb.gaussian(100, 1) @@ -61,7 +64,7 @@ class VerifySyntheticDiffInDiffEstimator } private lazy val df = data.toDF("Unit", "Time", "treatment", "postTreatment", "outcome") - + private lazy val estimator = new SyntheticDiffInDiffEstimator() .setTreatmentCol("treatment") .setPostTreatmentCol("postTreatment") @@ -74,7 +77,7 @@ class VerifySyntheticDiffInDiffEstimator // .setLocalSolverThreshold(1) test("SyntheticDiffInDiffEstimator can estimate the treatment effect") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val summary = estimator.fit(df).getSummary assert(summary.timeIntercept.get === 4.948917186627611) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala index e8f9fb74be..ae4f59e83a 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.{MatrixEntry, VectorEntry} diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala index 824471e74c..bb9171905e 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyMatrixOps.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} @@ -17,8 +20,8 @@ class VerifyMatrixOps extends TestBase { private lazy val testBzMatrix = BDM.rand(10, 20, RandBasis.withSeed(79).gaussian) private lazy val testDMatrix = testBzMatrix.toDMatrix - implicit val BDVEquality: Equality[BDV[Double]] = breezeVectorEq(1E-8) - implicit val BDMEquality: Equality[BDM[Double]] = breezeMatrixEq(1E-8) + implicit val equalityBDV: Equality[BDV[Double]] = breezeVectorEq(1E-8) + implicit val equalityBDM: Equality[BDM[Double]] = breezeMatrixEq(1E-8) test("MatrixOps.size computes correctly") { assert(DMatrixOps.size(testDMatrix) === BzMatrixOps.size(testBzMatrix)) @@ -30,7 +33,7 @@ class VerifyMatrixOps extends TestBase { } test("MatrixOps.colMean computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps assert(DMatrixOps.colMean(testDMatrix).toBreeze === BzMatrixOps.colMean(testBzMatrix)) } @@ -40,9 +43,9 @@ class VerifyMatrixOps extends TestBase { } test("MatrixOps.gemv computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val result1 = DMatrixOps.gemv(testDMatrix, testDVector1, Some(testDVector2), 2.0, 3.0).toBreeze val result2 = BzMatrixOps.gemv(testBzMatrix, testBzVector1, Some(testBzVector2), 2.0, 3.0) assert(result1 === result2) } -} \ No newline at end of file +} diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala index 768bab89b3..b7b0a42c3d 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/linalg/VerifyVectorOps.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.linalg import breeze.linalg.{DenseVector => BDV} @@ -14,8 +17,8 @@ class VerifyVectorOps extends TestBase { private lazy val testBzVector2 = BDV.rand(20, RandBasis.withSeed(47).gaussian) private lazy val testDVector2 = testBzVector2.toDVector - implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) - implicit val BDVEquality: Equality[BDV[Double]] = breezeVectorEq(1E-8) + implicit val equalityDouble: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + implicit val equalityBDV: Equality[BDV[Double]] = breezeVectorEq(1E-8) test("VectorOps.nrm2 computes correctly") { @@ -39,31 +42,31 @@ class VerifyVectorOps extends TestBase { } test("VectorOps.axpy computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val result = DVectorOps.axpy(testDVector1, Some(testDVector2), 2.5) assert(result.toBreeze === BzVectorOps.axpy(testBzVector1, Some(testBzVector2), 2.5)) } test("VectorOps.center computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val result = DVectorOps.center(testDVector1) assert(result.toBreeze === BzVectorOps.center(testBzVector1)) } test("VectorOps.elementwiseProduct computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val result = DVectorOps.elementwiseProduct(testDVector1, testDVector2) assert(result.toBreeze === BzVectorOps.elementwiseProduct(testBzVector1, testBzVector2)) } test("VectorOps.exp computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val result = DVectorOps.exp(testDVector1) assert(result.toBreeze === BzVectorOps.exp(testBzVector1)) } test("VectorOps.uniformRandom computes correctly") { - implicit val VectorOps: VectorOps[DVector] = DVectorOps + implicit val vectorOps: VectorOps[DVector] = DVectorOps val result1 = DVectorOps.make(20, 1d/20).toBreeze val result2 = BzVectorOps.make(20, 1d/20) assert(result1 === result2) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala index ab198aa2d6..04dd4cbc3f 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyConstrainedLeastSquare.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.opt import breeze.stats.distributions.RandBasis @@ -14,7 +17,7 @@ class VerifyConstrainedLeastSquare extends TestBase { private implicit val matrixOps: MatrixOps[BDM[Double], BDV[Double]] = BzMatrixOps private implicit val vectorOps: VectorOps[BDV[Double]] = BzVectorOps private implicit val cacheOps: CacheOps[BDV[Double]] = BDVCacheOps - private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + private implicit val equalityDouble: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) spark.sparkContext.setLogLevel("INFO") Logger.getLogger("org").setLevel(Level.WARN) diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala index 8728d4a66c..59ae4d4c2a 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/opt/VerifyMirrorDescent.scala @@ -1,3 +1,6 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in project root for information. + package com.microsoft.azure.synapse.ml.causal.opt import breeze.linalg.{sum, DenseMatrix => BDM, DenseVector => BDV} @@ -13,7 +16,7 @@ class VerifyMirrorDescent extends TestBase { private val vectorB = BDV.rand(100, RandBasis.withSeed(59).uniform) private implicit val matrixOps: MatrixOps[BDM[Double], BDV[Double]] = BzMatrixOps private implicit val vectorOps: VectorOps[BDV[Double]] = BzVectorOps - private implicit val DoubleEquality: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) + private implicit val equalityDouble: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1E-8) private def getLossFunc(A: BDM[Double], b: BDV[Double]) : DiffFunction[BDV[Double]] = { diff --git a/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala b/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala index c4045c5d4d..6e1ca4a21b 100644 --- a/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala +++ b/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala @@ -47,6 +47,7 @@ class FuzzingTest extends TestBase { "com.microsoft.azure.synapse.ml.cognitive.text.TextAnalyze", "com.microsoft.azure.synapse.ml.cognitive.text.TextAnalyze", "com.microsoft.azure.synapse.ml.causal.DoubleMLModel", + "com.microsoft.azure.synapse.ml.causal.DiffInDiffModel", "com.microsoft.azure.synapse.ml.causal.OrthoForestDMLModel", "com.microsoft.azure.synapse.ml.cognitive.DocumentTranslator", "com.microsoft.azure.synapse.ml.cognitive.translate.DocumentTranslator", @@ -103,6 +104,7 @@ class FuzzingTest extends TestBase { val exemptions: Set[String] = Set( "com.microsoft.azure.synapse.ml.cognitive.text.TextAnalyze", "com.microsoft.azure.synapse.ml.cognitive.translate.DocumentTranslator", + "com.microsoft.azure.synapse.ml.causal.DiffInDiffModel", "com.microsoft.azure.synapse.ml.automl.BestModel", "com.microsoft.azure.synapse.ml.automl.TuneHyperparameters", "com.microsoft.azure.synapse.ml.automl.TuneHyperparametersModel", @@ -213,6 +215,7 @@ class FuzzingTest extends TestBase { "com.microsoft.azure.synapse.ml.cognitive.translate.DocumentTranslator", "com.microsoft.azure.synapse.ml.automl.TuneHyperparameters", "com.microsoft.azure.synapse.ml.causal.DoubleMLModel", + "com.microsoft.azure.synapse.ml.causal.DiffInDiffModel", "com.microsoft.azure.synapse.ml.causal.OrthoForestDMLModel", "com.microsoft.azure.synapse.ml.train.TrainedRegressorModel", "com.microsoft.azure.synapse.ml.train.TrainedClassifierModel", From 0f37f60422ebd0ec5ffab83f0300f9a4c5404c6e Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 21:39:18 -0700 Subject: [PATCH 23/41] Python style fix Signed-off-by: Jason Wang --- .../synapse/ml/causal/DiffInDiffModel.py | 16 +- ... Synthetic difference in differences.ipynb | 162 ++++++++++-------- 2 files changed, 101 insertions(+), 77 deletions(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 7e16809c5f..07a86feda2 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -33,7 +33,15 @@ def __init__(self, java_obj=None) -> None: self.standardError = self.summary.standardError() self.timeIntercept = DiffInDiffModel._unwrapOption(self.summary.timeIntercept()) self.unitIntercept = DiffInDiffModel._unwrapOption(self.summary.unitIntercept()) - self.timeWeights = DiffInDiffModel._mapOption(java_obj.getTimeWeights(), lambda x: DataFrame(x, sql_ctx)) - self.unitWeights = DiffInDiffModel._mapOption(java_obj.getUnitWeights(), lambda x: DataFrame(x, sql_ctx)) - self.lossHistoryTimeWeights = DiffInDiffModel._unwrapOption(self.summary.getLossHistoryTimeWeightsJava()) - self.lossHistoryUnitWeights = DiffInDiffModel._unwrapOption(self.summary.getLossHistoryUnitWeightsJava()) + self.timeWeights = DiffInDiffModel._mapOption( + java_obj.getTimeWeights(), lambda x: DataFrame(x, sql_ctx) + ) + self.unitWeights = DiffInDiffModel._mapOption( + java_obj.getUnitWeights(), lambda x: DataFrame(x, sql_ctx) + ) + self.lossHistoryTimeWeights = DiffInDiffModel._unwrapOption( + self.summary.getLossHistoryTimeWeightsJava() + ) + self.lossHistoryUnitWeights = DiffInDiffModel._unwrapOption( + self.summary.getLossHistoryUnitWeightsJava() + ) diff --git a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb index cd0f9aa46e..08a6cf3095 100644 --- a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb +++ b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb @@ -38,7 +38,11 @@ "outputs": [], "source": [ "from pyspark.sql.types import *\n", - "from synapse.ml.causal import DiffInDiffEstimator, SyntheticControlEstimator, SyntheticDiffInDiffEstimator\n", + "from synapse.ml.causal import (\n", + " DiffInDiffEstimator,\n", + " SyntheticControlEstimator,\n", + " SyntheticDiffInDiffEstimator,\n", + ")\n", "from matplotlib import pyplot as plt\n", "from matplotlib import style\n", "import pandas as pd\n", @@ -72,8 +76,12 @@ }, "outputs": [], "source": [ - "df = spark.read.option(\"header\", True).option(\"inferSchema\", True).csv(\"wasbs://publicwasb@mmlspark.blob.core.windows.net/smoking.csv\") \\\n", - " .select(\"state\", \"year\", \"cigsale\", \"california\", \"after_treatment\")\n", + "df = (\n", + " spark.read.option(\"header\", True)\n", + " .option(\"inferSchema\", True)\n", + " .csv(\"wasbs://publicwasb@mmlspark.blob.core.windows.net/smoking.csv\")\n", + " .select(\"state\", \"year\", \"cigsale\", \"california\", \"after_treatment\")\n", + ")\n", "display(df)" ] }, @@ -100,7 +108,10 @@ }, "outputs": [], "source": [ - "estimator1 = DiffInDiffEstimator(treatmentCol=\"california\", postTreatmentCol=\"after_treatment\", outcomeCol = \"cigsale\")\n", + "estimator1 = DiffInDiffEstimator(\n", + " treatmentCol=\"california\", postTreatmentCol=\"after_treatment\", outcomeCol=\"cigsale\"\n", + ")\n", + "\n", "model1 = estimator1.fit(df)\n", "\n", "print(\"[Diff in Diff] treatment effect: {}\".format(model1.treatmentEffect))\n", @@ -138,8 +149,16 @@ "outputs": [], "source": [ "estimator2 = SyntheticControlEstimator(\n", - " timeCol = \"year\", unitCol = \"state\", treatmentCol = \"california\", postTreatmentCol = \"after_treatment\", outcomeCol = \"cigsale\", \n", - " maxIter = 5000, numIterNoChange = 50, tol = 1E-4, stepSize = 1.0)\n", + " timeCol=\"year\",\n", + " unitCol=\"state\",\n", + " treatmentCol=\"california\",\n", + " postTreatmentCol=\"after_treatment\",\n", + " outcomeCol=\"cigsale\",\n", + " maxIter=5000,\n", + " numIterNoChange=50,\n", + " tol=1e-4,\n", + " stepSize=1.0,\n", + ")\n", "\n", "model2 = estimator2.fit(df)\n", "\n", @@ -180,9 +199,9 @@ "lossHistory = pd.Series(np.array(model2.lossHistoryUnitWeights))\n", "\n", "plt.plot(lossHistory[2000:])\n", - "plt.title('loss history - unit weights')\n", - "plt.xlabel('Iteration')\n", - "plt.ylabel('Loss')\n", + "plt.title(\"loss history - unit weights\")\n", + "plt.xlabel(\"Iteration\")\n", + "plt.ylabel(\"Loss\")\n", "plt.show()\n", "\n", "print(\"Mimimal loss: {}\".format(lossHistory.min()))" @@ -213,14 +232,25 @@ "source": [ "sc_weights = model2.unitWeights.toPandas().set_index(\"state\")\n", "pdf = df.toPandas()\n", - "sc = pdf.query(\"~california\").pivot(index=\"year\", columns=\"state\", values=\"cigsale\").dot(sc_weights)\n", - "\n", + "sc = (\n", + " pdf.query(\"~california\")\n", + " .pivot(index=\"year\", columns=\"state\", values=\"cigsale\")\n", + " .dot(sc_weights)\n", + ")\n", "plt.plot(sc, label=\"Synthetic Control\")\n", "plt.plot(sc.index, pdf.query(\"california\")[\"cigsale\"], label=\"California\", color=\"C1\")\n", "\n", "plt.title(\"Synthetic Control Estimation\")\n", "plt.ylabel(\"Cigarette Sales\")\n", - "plt.vlines(x=1988, ymin=40, ymax=140, linestyle=\":\", lw=2, label=\"Proposition 99\", color=\"black\")\n", + "plt.vlines(\n", + " x=1988,\n", + " ymin=40,\n", + " ymax=140,\n", + " linestyle=\":\",\n", + " lw=2,\n", + " label=\"Proposition 99\",\n", + " color=\"black\",\n", + ")\n", "plt.legend()" ] }, @@ -248,8 +278,16 @@ "outputs": [], "source": [ "estimator3 = SyntheticDiffInDiffEstimator(\n", - " timeCol = \"year\", unitCol = \"state\", treatmentCol = \"california\", postTreatmentCol = \"after_treatment\", outcomeCol = \"cigsale\", \n", - " maxIter = 5000, numIterNoChange = 50, tol = 1E-4, stepSize = 1.0)\n", + " timeCol=\"year\",\n", + " unitCol=\"state\",\n", + " treatmentCol=\"california\",\n", + " postTreatmentCol=\"after_treatment\",\n", + " outcomeCol=\"cigsale\",\n", + " maxIter=5000,\n", + " numIterNoChange=50,\n", + " tol=1e-4,\n", + " stepSize=1.0,\n", + ")\n", "\n", "model3 = estimator3.fit(df)\n", "\n", @@ -290,9 +328,9 @@ "lossHistory = pd.Series(np.array(model3.lossHistoryUnitWeights))\n", "\n", "plt.plot(lossHistory[1000:])\n", - "plt.title('loss history - unit weights')\n", - "plt.xlabel('Iteration')\n", - "plt.ylabel('Loss')\n", + "plt.title(\"loss history - unit weights\")\n", + "plt.xlabel(\"Iteration\")\n", + "plt.ylabel(\"Loss\")\n", "plt.show()\n", "\n", "print(\"Mimimal loss: {}\".format(lossHistory.min()))" @@ -317,9 +355,9 @@ "lossHistory = pd.Series(np.array(model3.lossHistoryTimeWeights))\n", "\n", "plt.plot(lossHistory[1000:])\n", - "plt.title('loss history - time weights')\n", - "plt.xlabel('Iteration')\n", - "plt.ylabel('Loss')\n", + "plt.title(\"loss history - time weights\")\n", + "plt.xlabel(\"Iteration\")\n", + "plt.ylabel(\"Loss\")\n", "plt.show()\n", "\n", "print(\"Mimimal loss: {}\".format(lossHistory.min()))" @@ -355,15 +393,19 @@ "time_intercept = model3.timeIntercept\n", "\n", "pdf = df.toPandas()\n", - "pivot_df_control = pdf.query(\"~california\").pivot(index='year', columns='state', values='cigsale')\n", - "pivot_df_treat = pdf.query(\"california\").pivot(index='year', columns='state', values='cigsale')\n", + "pivot_df_control = pdf.query(\"~california\").pivot(\n", + " index=\"year\", columns=\"state\", values=\"cigsale\"\n", + ")\n", + "pivot_df_treat = pdf.query(\"california\").pivot(\n", + " index=\"year\", columns=\"state\", values=\"cigsale\"\n", + ")\n", "sc_did = pivot_df_control.values @ unit_weights.values\n", "treated_mean = pivot_df_treat.mean(axis=1)" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false, @@ -375,64 +417,38 @@ } } }, - "outputs": [ - { - "data": { - "application/vnd.livy.statement-meta+json": { - "execution_finish_time": "2023-10-12T00:10:00.2809196Z", - "execution_start_time": "2023-10-12T00:09:59.110083Z", - "livy_statement_state": "available", - "parent_msg_id": "391a813b-98ff-49a4-b247-6f3aa60587f1", - "queued_time": "2023-10-12T00:07:18.6147362Z", - "session_id": "42", - "session_start_time": null, - "spark_jobs": null, - "spark_pool": "jasowang33", - "state": "finished", - "statement_id": 15 - }, - "text/plain": [ - "StatementMeta(jasowang33, 42, 15, Finished, Available)" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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S8Yc8DXuuOMOAN59910sFkvs+OjRo7noooti35eXl3PXXXexbNkyNBoN8+fPZ9GiRaSlpQHw2GOPsWTJEq6++moeffRRnE4nCxYs4Mknn8RoNHLjjTeyfPlyli9fzt///ncAVqxYQWlpKRdccAEvvvgijzzyCJs3b+bFF19k+vTpLFq0iLfeegu3282kSZO49957mTJlygE9vuXLl+Pz+bj99tt55ZVXWLFiBbNnz8blcjF16lT+9re/sWDBgtj49957j1/84hesW7cOq9XKqlWruOOOO9i5cyejR4/mhhtu4IorruCDDz5gwoQJB/x8i/gg00iFEKIfRVSVquYAbv/Bv+MohBBCCCHEYNTY2Minn37Kj3/84w5BW5uEhAQg2m12+eWX09TUxOuvv85LL73Enj17uOaaazqM37NnDx988AH//Oc/+ec//8mKFSt46qmnALj//vs56qijuOSSS1i7di1r164lOzs7dt9Fixbx61//ms8++4yxY8fywAMP8N577/Hkk0+yZMkShg8fziWXXEJjY+MBPcaXXnqJs88+G71ez1lnncVLL70EgMPhYOHChfznP//pMP6NN97g5JNPxmq14na7+fGPf8yYMWNYsmQJt912Gw888MABXV/EJ+lsE0KIfuAJhllV5uar0mbqWkLoV9SQaID8RAPDEo0MSzCSZTeg1SgDXaoQQgghhBD9Yvfu3aiqyqhRo3oc98UXX7B582aWL19OTk4OEF0HbcGCBXz77bexbrNIJMITTzyBzWYD4LzzzuOLL77gtttuw+FwYDAYMJlMpKend7rGbbfdxty5cwHweDwsXryYJ554goULFwLw6KOPMnv2bP797393Cvm609zczHvvvcdbb70FwLnnnsvZZ5/NokWLsNvtnHvuudxwww14vV7MZjPNzc188skn/PWvfwXgP//5D4qi8Oijj2IymSgqKqKqqorbbrutV9cX8UvCNiGE6EPlrgBf7nGxuqKFYCS6yoWuNVCrbQlS0xLkm/IWAPQahdyE1vCtNYBLMGlRFAnghBBCCCHE4Keq0d+H9/f77fbt28nOzo4FbQBFRUUkJCSwffv2WNiWl5cXC9oA0tPTqa2t7VUtkyZNin29e/dugsEgM2bMiB3T6/VMmTKF7du39+p8EO1SGzZsGOPHjwdgwoQJDBs2jLfeeotLL72U448/Hp1O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Zszg8ssvZ9KkSbHbTjzxRH7/+9/zxhtvcN555/XbcyBhm+gzgXCELbXRgG1TjQdvaO/i3ha9hvHpZiZlWClKNaHXSsAmhBCDWZJZx5w8O3Py7IQjKnua/NHwrc5LqTNAeXP045NdLoxahVEpptiU07Z1OIUQQggx9F111VUEg0GefPJJhg0bRm1tLcuWLaOpqWmgSzskSUlJh3T/devWceGFFzJ69GgeeeQRRo0ahdvt5sMPP+T+++/n9ddf76NKeyccDqMoSp90e7333ntYLBZmzZoVO3b77bfz7bffsmjRIoqKimhsbOSbb76hsbGxw30vueQSbr31VoLBIFVVVSxZsoRrr72W73//+/zud7/r8bqPP/44CxYswO/3s2vXLv71r39xxhln8Nhjj3HBBRfExl144YX84x//kLBNxC9vMMKmmugOoptrvQQjewM2u1HLxAwLkzIsjEw2oZWuBiGEGJK0GoURySZGJJs4rSiJZn+YbXXR6abb6rw0ByJsrPGyscYLQJpVx+hUM2NTzYxMMWGQN2CEEEKIA6aqKoFw593L+5tB2/vXdU6nk5UrV/Laa68xZ84cAHJzc5k6dWpszM0330xdXR2LFy+OHQuFQkyfPp1f/epXXHTRRZx//vmMHTsWo9HISy+9hF6v54c//CG33HILQCzUueKKK2LX+Prrr2Pne+2113j00UdxOp0sWLCARx99FJvNBkSfx6effpoXXniBmpoaCgoKuPHGGznjjDMoLS2NhTTjxo0D4IILLuDJJ5/sNI3U7/fz4IMP8uabb1JfX092djbXXXcdP/jBDzo9L6qqctNNN1FQUMAbb7zRIeCaMGFC7HEAbN68mbvvvps1a9ZgMpk4/fTTueeee7BarQDceOONuFwuZs6cyTPPPEMgEOCcc87hnnvuQa/Xc/7551NWVsa9997LvffeC0B5eXlsOuhTTz3FAw88wK5du1i2bBkOh4O7776bjz/+GL/fz5w5c7j//vsZMWJEr//c33rrLU466aQOxz7++GPuu+8+jj/+eADy8vI6dJy1MZlMpKenA9FutaOOOopRo0Zx8803c8YZZzB37txur5uQkBC7b15eHvPmzeOGG27grrvu4sQTTyQxMRGAk046id/85jfs2bOHYcOG9fpxHQgJ28QBc/vDbKjxsL7aw/Y6L+3/f08266IBW6aFYYlG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My2+4K8Wb+etrzv8PWvNtFj12NbscbFi3pppqeu89dfPrL9mrZkWqx7bmmnxdt4ahqGFCxeqQ4cOuu666yTJva+fz8ro6GgVFha6twkODq60IH/5LPXFvLUbFtug4OBg/f73v1deXp7S0tL00EMPKSsrS926dfNY5b7k4sWL+uqrr0yfSWYYhhwOhy1azPJHx5kzZzRjxgzdcsst6tu3b8Ba7r//fs2cOVN/+MMfFBQUpLlz58owDEtbPv30U5WUlGjw4MGmvq4/WyRpwIAB6tSpk66//nr17dtXY8eO1caNG3Xu3DnLOi79HQwZMkQ9e/ZUQkKCxo8fL0n6+9//bvkxueTHH3/UP//5T915552mGnzdYhiG3n77bUVHR2vatGmaMWOGunfvrldeecX0/3T5qqV169aaMGGCVq1apYceekjjxo1T8+bNFR0dXeV+atuxdOlSnT9/Xs8995xefvllDRgwQK+//roOHz78i/v3x6ytbYs3/NHi73lrtsWKeVtTS13nrS+PSV1mrS9brJy33vzc+nvemjkudZm3vuqo66w12xIVFaX/+I//0DfffKOHH35YqampunDhgtq2bVvj1/H1vK1Lizf80VKbeevrDn/PWjMtVj22NXtcrJi3ZlrqOm/98TPrz1lr9phY8djWTIu383bBggU6fPiwJk6cWOlzP5+JZv4Nmt3G7Ly1G65GCklSQkKCZs2apQsXLsjlcikqKkrPPPNMlaeybtu2TWVlZerdu7fH7TExMe7V68sVFRVVuUrtzxZf8GXHmTNnNG3aNCUmJmrcuHEBbYmKilJUVJRatGihli1b6rHHHtMPP/ygxMREy1r27NmjnJwcjRgxwuP2p556Srfffnu1V4X0R0tVLh2LEydOqHHjxpZ0XLoqUKtWrdy3hYSEqHnz5srPz6+xwZctl9u0aZMaN26sm266yXSDL1v27Nmjb775Ru+8844iIiLc+83MzNSWLVs0aNAgy1ok6fbbb9ftt9+ugoIC91WaVq9erWuvvdYnHSdOnNBnn32m1157zX0FpzZt2uj777/XZ599pnHjxlk2a820+IovW/w9b71p8fe8NdPii3nrr58Vb2etr1qsmrfeHhd/zluzPyt1nbe+OiZ1nbVmWiSpS5cumjNnjoqKitSgQQNFRkZq7Nix7q9j5WPbmlp8xZctdZm3vuyw4rFtTS1WPratzc+KP+atmRZfzFtfHxN/P7Y187Ni1WNbM8fF7Lz961//qm+++UbTpk3zuILopb/jgoICNWnSxH375XMyJibGfebc5We3FRUVuV9F4qt5ayec2QYPERERioqKUl5envbv36/u3btX2mbjxo266aabFBUV5XF7YmKiLly4oH379rlv++GHH3ThwoVavcSzLi2+VNeOSw9E2rZtq/Hjx9fpGUpfH5NLzyY4nU5LW9LS0jRr1iy9+uqrevXVV/X0009LkiZNmqQHH3zQ0paqXLoi2uW/MPzdkZCQoJCQEB0/ftx9m8vl0unTp9WsWTOvOuracolhGNq8ebN69epl+r1PfN1SVlYmSZX+3TgcjiqvAObPlsvFxMQoLCxMW7duVWhoqMd7UNSl4+LFi5IqPzsYFBTk/vdq1aw10+JrdW2xYt7W9rj4Y96aafHlvPX1MantrK1ri1Xz1pvj4u95a6bFl/PWVz8rdZ21v9RyuaioKEVGRmrPnj0qKipy/094IB7bVtfia3Vt8dW89fUx8fdj2+paAvHY1pvj4o95a6bFl/PWF8fEyse21bUE4rGtmZ+V6uatYRhasGCBtm/frueff77SIty1116rmJgYZWZmum9zuVzKzs52z8mEhAQ1aNDAY5uzZ8/q8OHD7oVgX89bO+DMtqtEaWmpTpw44f741KlTys3NVaNGjRQbG6u///3vioqKUmxsrA4fPqz09HR1797d440OpZ+eDfnuu+/cv0Au16pVK3Xt2lVvvfWWxo4dK0maP3++brzxRrVo0cLSFknKz89XcXGx8vPzVVFRodzcXElSXFycwsLCLOk4c+aM/vjHPyo2NlYPP/ywioqK3J+79CyAVcdk37592rdvnzp06KDIyEidPHlSH3zwgZo3b+7xzJ8VLT9/f4RLz6LExcV5PFNiRUtOTo5ycnLUuXNnRUREaN++fVq4cKFuuukmd6cVHREREerXr58++OADXXPNNWrWrJlWrlwpSerZs6elx+SSPXv26NSpU9W+zNSKlsTERDVq1Ehz587V0KFDFRoaqg0bNujUqVO68cYbLT8un332mRITExUWFqbMzEwtXrxYI0aMcL/HRl07WrRoobi4OP3lL3/R7373OzVq1Ej/+Mc/lJmZqalTp0qybtaaaZFqnrVWtVg1b820WDVvzbSYmbdWdJiZtVa1WDVvzf4bkvw/b820mJm3Vh2TmmatL1qkn85wadmypaKiopSTk6P09HTde++97llq5WPbmloka+atmRYz89aKDisf29bUYuVj25parJq3ZlrMzFur/v1I1jy2ranFyse2Zo5LTfN2wYIF+uqrr/Tkk08qPDzc/f5pERER7gsu3HPPPVqxYoXi4+MVFxenFStWqGHDhrr99tvd2/bp00eLFi1S48aN1ahRIy1atEjXXXede1HP7Ly9kjgMfz09DVvJysrStGnTKt3eu3dvTZgwQZ988olWrVrlPv2zV69eGjp0aKUV/yVLlujLL7/UvHnzqnwGq7i42H2KqST9y7/8ix555BGPB0dWtcybN09btmypdPsLL7yg5ORkSzo2b96sN998s9LXkKQPPvjA0mNy+PBhvfPOOzp06JDKysoUExOjrl276re//a2aNm1qacvPnTp1So8//rheffVVtWnTxtKWAwcOaMGCBTp27JicTqeaNWumW2+9VQMHDlTDhg0tPSYul8u9zcWLF9WuXTulpqa6X2pjZYsk/fnPf1Z+fr6mT59e5eetatm/f7+WLl2q/fv3q7y8XK1atdLQoUPVrVs3y1vmzp2rXbt2qbS0VC1bttR9992nXr16+bQjLy9P7733nvbu3avS0lLFxcVV+jpWzVozLTXNWqtarJy3NbVYOW/N/B1drqp5a0WHmVlr5TGxat6a/fuxYt6aaalp3lrVUdOs9VXLe++9p82bN6u4uFjXXnut+vXrp3vvvdfjzDur5q2ZFqvmbU0tZuatFR1Wzlozfz+X8+dj25parJy3Zo5LTfPWyr8fK2atmRarHtuaaalp3g4fPrzKYzV+/Hj31VwNw9Dy5cu1fv16nT9/Xu3atdMjjzzivoiC9NOZzYsXL9ZXX32lixcvqnPnzhozZozHArCZeXslYbENAAAAAAAA8BHesw0AAAAAAADwERbbAAAAAAAAAB9hsQ0AAAAAAADwERbbAAAAAAAAAB9hsQ0AAAAAAADwERbbAAAAAAAAAB9hsQ0AAAAAAADwERbbAAAAAAAAAB8JDnQAAAAAfGv48OGmtnvhhRe0efNmZWdna968eX6uAgAAuDo4DMMwAh0BAAAA38nJyfH4+KOPPlJWVpaef/55j9tbtWqloqIilZSUqG3btlYmAgAA1Fuc2QYAAFDPJCYmenwcFRUlh8NR6XZJioiIsCoLAADgqsBiGwAAwFVs3rx5lV5GOnz4cPXv318JCQnKyMhQfn6+WrdurbS0NLVv316rVq3S2rVrVVRUpHbt2unRRx9VXFycx34zMzOVkZGh/fv3q7y8XG3bttXw4cN1ww03WP0tAgAAWIoLJAAAAKCSXbt2aePGjRo5cqQmTpyokpISvfLKK3r33Xf1/fff65FHHtGjjz6qo0eP6rXXXtPl70zyxRdf6KWXXlJ4eLgmTJigyZMnKzIyUi+99JK+/fbbAH5XAAAA/seZbQAAAKjE6XTq2WefVVhYmCTJ4XBo1qxZysrK0syZM+VwOCRJRUVFSk9P15EjR3TdddeprKxM6enpuvHGGzVlyhT3/rp166apU6fq/fff5+w2AABQr3FmGwAAACpJTk52L7RJUsuWLSVJXbt2dS+0XX776dOnJUl79+5VcXGxevfurfLycvd/hmGoa9eu2r9/v0pLSy38TgAAAKzFmW0AAACopFGjRh4fBwcH/+LtTqdTklRYWChJev3116vdd3FxscdCHgAAQH3CYhsAAAB8pnHjxpLkvphCVWJiYiwsAgAAsBaLbQAAAPCZDh06KDIyUkePHtXdd98d6BwAAADLsdgGAAAAnwkLC9Po0aM1b948FRcXq2fPnoqKilJRUZEOHTqkoqIijR07NtCZAAAAfsNiGwAAAHyqV69eio2N1cqVKzV//nyVlJQoOjpabdq00R133BHoPAAAAL9yGIZhBDoCAAAAAAAAqA+CAh0AAAAAAAAA1BcstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CMstgEAAAAAAAA+wmIbAAAAAAAA4CP/D7BtpE2/lsyCAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15,8), sharex=True, gridspec_kw={'height_ratios': [3, 1]})\n", + "fig, (ax1, ax2) = plt.subplots(\n", + " 2, 1, figsize=(15, 8), sharex=True, gridspec_kw={\"height_ratios\": [3, 1]}\n", + ")\n", "fig.suptitle(\"Synthetic Diff in Diff Estimation\")\n", "\n", - "ax1.plot(pivot_df_control.mean(axis=1), lw=3, color=\"C1\", ls=\"dashed\", label=\"Control Avg.\")\n", + "ax1.plot(\n", + " pivot_df_control.mean(axis=1), lw=3, color=\"C1\", ls=\"dashed\", label=\"Control Avg.\"\n", + ")\n", "ax1.plot(treated_mean, lw=3, color=\"C0\", label=\"California\")\n", - "ax1.plot(pivot_df_control.index, sc_did, label=\"Synthetic Control (SDID)\", color=\"C1\", alpha=.8)\n", + "ax1.plot(\n", + " pivot_df_control.index,\n", + " sc_did,\n", + " label=\"Synthetic Control (SDID)\",\n", + " color=\"C1\",\n", + " alpha=0.8,\n", + ")\n", "ax1.set_ylabel(\"Cigarette Sales\")\n", - "ax1.vlines(1989, treated_mean.min(), treated_mean.max(), color=\"black\", ls=\"dotted\", label=\"Prop. 99\")\n", + "ax1.vlines(\n", + " 1989,\n", + " treated_mean.min(),\n", + " treated_mean.max(),\n", + " color=\"black\",\n", + " ls=\"dotted\",\n", + " label=\"Prop. 99\",\n", + ")\n", "ax1.legend()\n", "\n", - "ax2.bar(time_weights.index, time_weights['value'], color='skyblue')\n", + "ax2.bar(time_weights.index, time_weights[\"value\"], color=\"skyblue\")\n", "ax2.set_ylabel(\"Time Weights\")\n", - "ax2.set_xlabel(\"Time\");\n", + "ax2.set_xlabel(\"Time\")\n", "ax2.vlines(1989, 0, 1, color=\"black\", ls=\"dotted\")" ] } From aa6728a9ea47b2aa8386356635a61e515745361b Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 21:42:54 -0700 Subject: [PATCH 24/41] fix unit test --- .../azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala b/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala index 6e1ca4a21b..5771bcd45d 100644 --- a/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala +++ b/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala @@ -162,6 +162,7 @@ class FuzzingTest extends TestBase { "com.microsoft.azure.synapse.ml.cognitive.translate.DocumentTranslator", "com.microsoft.azure.synapse.ml.automl.TuneHyperparameters", "com.microsoft.azure.synapse.ml.causal.DoubleMLModel", + "com.microsoft.azure.synapse.ml.causal.DiffInDiffModel", "com.microsoft.azure.synapse.ml.causal.OrthoForestDMLModel", "com.microsoft.azure.synapse.ml.train.TrainedRegressorModel", "com.microsoft.azure.synapse.ml.train.TrainedClassifierModel", From c1eeaff1ea202f2421b7de3ecec2b8dcccae672f Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 26 Oct 2023 22:34:53 -0700 Subject: [PATCH 25/41] fix more python style issue --- core/src/main/python/synapse/ml/causal/DiffInDiffModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 07a86feda2..8f6d916e9a 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -23,7 +23,7 @@ def _unwrapOption(option): return DiffInDiffModel._mapOption(option, lambda x: x) def __init__(self, java_obj=None) -> None: - super(DiffInDiffModel, self).__init__(java_obj = java_obj) + super(DiffInDiffModel, self).__init__(java_obj=java_obj) ctx = SparkContext._active_spark_context sql_ctx = SQLContext.getOrCreate(ctx) From b0326a77588441cf07f7b0afe1c51a47c754f4d7 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 27 Oct 2023 10:49:35 -0700 Subject: [PATCH 26/41] python style fix --- core/src/main/python/synapse/ml/causal/DiffInDiffModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 8f6d916e9a..8d781045ca 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -11,9 +11,9 @@ from pyspark.sql import SparkSession, DataFrame from pyspark import SparkContext, SQLContext + @inherit_doc class DiffInDiffModel(_DiffInDiffModel): - @staticmethod def _mapOption(option, func): return func(option.get()) if option.isDefined() else None From df296a648979d0905a03ef23d74c2aa3a544c16c Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 27 Oct 2023 12:05:51 -0700 Subject: [PATCH 27/41] fix unit test --- .../azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index c263cbf3d5..fcb10389e5 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -62,7 +62,7 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) .setLabelCol(getOutcomeCol) .setFitIntercept(fitIntercept) .setLoss("squaredError") - .setRegParam(0.0) + .setRegParam(1E-10) assembler.transform _ andThen regression.fit apply df } From 280ab2d099a66dd3f5867ac129fea9437edc3416 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 11:16:47 -0700 Subject: [PATCH 28/41] Update core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala Co-authored-by: Mark Hamilton --- .../microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index 086e394c1e..b1ea6652ab 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -19,7 +19,7 @@ class DiffInDiffEstimator(override val uid: String) logClass(FeatureNames.Causal) - def this() = this(Identifiable.randomUID("did")) + def this() = this(Identifiable.randomUID("DiffInDiffEstimator")) override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ val postTreatment = col(getPostTreatmentCol) From 638d9b4d82465be4739c1cdde35c43667c1fcf32 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 11:17:10 -0700 Subject: [PATCH 29/41] Update core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala Co-authored-by: Mark Hamilton --- .../azure/synapse/ml/causal/SyntheticControlEstimator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index cb5d0c7c3d..c28b463588 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -23,7 +23,7 @@ class SyntheticControlEstimator(override val uid: String) import SyntheticEstimator._ - def this() = this(Identifiable.randomUID("syncon")) + def this() = this(Identifiable.randomUID("SyntheticControlEstimator")) // scalastyle:off method.length override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ From 65536532845d20a8a5d929a72acd9587e9e8a783 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 11:17:18 -0700 Subject: [PATCH 30/41] Update core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala Co-authored-by: Mark Hamilton --- .../azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 34210481a3..d099f31877 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -22,7 +22,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) import SyntheticEstimator._ - def this() = this(Identifiable.randomUID("syndid")) + def this() = this(Identifiable.randomUID("SyntheticDiffInDiffEstimator")) // scalastyle:off method.length override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ From 864a04fc621789facf366a06d54133203d417b2f Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 12:04:13 -0700 Subject: [PATCH 31/41] addressing comments --- .../ml/causal/BaseDiffInDiffEstimator.scala | 16 ++++++++++++---- .../synapse/ml/causal/DiffInDiffEstimator.scala | 2 ++ .../ml/causal/DiffInDiffEstimatorParams.scala | 11 ----------- .../ml/causal/SyntheticControlEstimator.scala | 4 +++- .../ml/causal/SyntheticDiffInDiffEstimator.scala | 4 +++- .../synapse/ml/causal/SyntheticEstimator.scala | 4 ++-- .../ml/causal/SyntheticEstimatorParams.scala | 16 ++++++++++++---- 7 files changed, 34 insertions(+), 23 deletions(-) delete mode 100644 core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index fcb10389e5..e4dcea66d5 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -5,11 +5,12 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.causal.linalg.DVector import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.core.schema.DatasetExtensions import com.microsoft.azure.synapse.ml.logging.{FeatureNames, SynapseMLLogging} import com.microsoft.azure.synapse.ml.param.DataFrameParam import org.apache.spark.SparkException import org.apache.spark.ml.feature.VectorAssembler -import org.apache.spark.ml.param.ParamMap +import org.apache.spark.ml.param.{ParamMap, Params} import org.apache.spark.ml.regression.LinearRegression import org.apache.spark.ml.util.Identifiable import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable, Estimator, Model} @@ -43,22 +44,24 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) override def copy(extra: ParamMap): Estimator[DiffInDiffModel] = defaultCopy(extra) - private[causal] val interactionCol = "interaction" + private[causal] val findInteractionCol = DatasetExtensions.findUnusedColumnName("interaction") _ private[causal] def fitLinearModel(df: DataFrame, featureCols: Array[String], fitIntercept: Boolean, weightCol: Option[String] = None) = { + + val featuresCol = DatasetExtensions.findUnusedColumnName("features", df) val assembler = new VectorAssembler() .setInputCols(featureCols) - .setOutputCol("features") + .setOutputCol(featuresCol) val regression = weightCol .map(new LinearRegression().setWeightCol) .getOrElse(new LinearRegression()) regression - .setFeaturesCol("features") + .setFeaturesCol(featuresCol) .setLabelCol(getOutcomeCol) .setFitIntercept(fitIntercept) .setLoss("squaredError") @@ -162,3 +165,8 @@ class DiffInDiffModel(override val uid: String) } object DiffInDiffModel extends ComplexParamsReadable[DiffInDiffModel] + +trait DiffInDiffEstimatorParams extends Params + with HasTreatmentCol + with HasOutcomeCol + with HasPostTreatmentCol diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala index b1ea6652ab..6fe92fb5fd 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimator.scala @@ -4,6 +4,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.core.schema.DatasetExtensions import com.microsoft.azure.synapse.ml.logging.{FeatureNames, SynapseMLLogging} import org.apache.spark.ml.util.Identifiable import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} @@ -22,6 +23,7 @@ class DiffInDiffEstimator(override val uid: String) def this() = this(Identifiable.randomUID("DiffInDiffEstimator")) override def fit(dataset: Dataset[_]): DiffInDiffModel = logFit({ + val interactionCol = findInteractionCol(dataset.columns.toSet) val postTreatment = col(getPostTreatmentCol) val treatment = col(getTreatmentCol) val outcome = col(getOutcomeCol) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala deleted file mode 100644 index a26f6128c1..0000000000 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/DiffInDiffEstimatorParams.scala +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (C) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in project root for information. - -package com.microsoft.azure.synapse.ml.causal - -import org.apache.spark.ml.param.Params - -trait DiffInDiffEstimatorParams extends Params - with HasTreatmentCol - with HasOutcomeCol - with HasPostTreatmentCol diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index c28b463588..5757935d3d 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -57,6 +57,8 @@ class SyntheticControlEstimator(override val uid: String) val indexedDf = df.join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") + val interactionCol = findInteractionCol(indexedDf.columns.toSet) + val weightsCol = findWeightsCol(indexedDf.columns.toSet) val didData = indexedDf.select( col(getTimeCol), col(UnitIdxCol), @@ -72,7 +74,7 @@ class SyntheticControlEstimator(override val uid: String) outcome, ( coalesce(col("u.value"), lit(1d / u)) + // unit weights - lit(epsilon) // avoid zero weights + lit(getEpsilon) // avoid zero weights ).as(weightsCol), (treatment * postTreatment).as(interactionCol) ) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index d099f31877..3ea9a3fbed 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -70,6 +70,8 @@ class SyntheticDiffInDiffEstimator(override val uid: String) val indexedDf = df.join(timeIdx, df(getTimeCol) === timeIdx(getTimeCol), "left_outer") .join(unitIdx, df(getUnitCol) === unitIdx(getUnitCol), "left_outer") + val interactionCol = findInteractionCol(indexedDf.columns.toSet) + val weightsCol = findWeightsCol(indexedDf.columns.toSet) val didData = indexedDf.select( col(UnitIdxCol), col(TimeIdxCol), @@ -86,7 +88,7 @@ class SyntheticDiffInDiffEstimator(override val uid: String) ( coalesce(col("t.value"), lit(1d / t)) * // time weights coalesce(col("u.value"), lit(1d / u)) + // unit weights - lit(epsilon) // avoid zero weights + lit(getEpsilon) // avoid zero weights ).as(weightsCol), (treatment * postTreatment).as(interactionCol) ) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 96b92ea08c..cb88f39c41 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -10,6 +10,7 @@ import org.apache.spark.sql.types._ import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} import com.microsoft.azure.synapse.ml.causal.linalg._ import com.microsoft.azure.synapse.ml.causal.opt.ConstrainedLeastSquare +import com.microsoft.azure.synapse.ml.core.schema.DatasetExtensions import com.microsoft.azure.synapse.ml.logging.SynapseMLLogging trait SyntheticEstimator extends SynapseMLLogging { @@ -25,8 +26,7 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] lazy val postTreatment = col(getPostTreatmentCol) private[causal] lazy val treatment = col(getTreatmentCol) private[causal] lazy val outcome = col(getOutcomeCol) - private[causal] val weightsCol = "weights" - private[causal] val epsilon = 1E-10 + private[causal] val findWeightsCol = DatasetExtensions.findUnusedColumnName("weights") _ private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, size: (Long, Long)) : (DVector, Double, Seq[Double]) = { diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala index 13a16df35c..68a1bbf316 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimatorParams.scala @@ -3,11 +3,9 @@ package com.microsoft.azure.synapse.ml.causal -import org.apache.spark.ml.param.{IntParam, LongParam, Param, ParamValidators, Params} +import org.apache.spark.ml.param.{DoubleParam, IntParam, LongParam, Param, ParamValidators, Params} import org.apache.spark.ml.param.shared.{HasMaxIter, HasStepSize, HasTol} -import scala.util.Random - trait SyntheticEstimatorParams extends Params with HasUnitCol with HasTimeCol @@ -48,6 +46,15 @@ trait SyntheticEstimatorParams extends Params /** @group expertGetParam */ def setLocalSolverThreshold(value: Long): this.type = set(localSolverThreshold, value) + final val epsilon = new DoubleParam(this, "epsilon", + "This value is added to the weights when we fit the final linear model for " + + "SyntheticControlEstimator and SyntheticDiffInDiffEstimator in order to avoid " + + "zero weights.", ParamValidators.gt(0d)) + + def getEpsilon: Double = $(epsilon) + + def setEpsilon(value: Double): this.type = set(epsilon, value) + def setMaxIter(value: Int): this.type = set(maxIter, value) def setStepSize(value: Double): this.type = set(stepSize, value) @@ -59,6 +66,7 @@ trait SyntheticEstimatorParams extends Params tol -> 1E-3, maxIter -> 100, handleMissingOutcome -> "zero", - localSolverThreshold -> 1000 * 1000 + localSolverThreshold -> 1000 * 1000, + epsilon -> 1E-10 ) } From 7c47d4246a9e57f9c4c72bcfd839c51a3330ba36 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 13:28:59 -0700 Subject: [PATCH 32/41] extract some constants to findUnusedColumn --- .../azure/synapse/ml/causal/SyntheticEstimator.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index cb88f39c41..77cd5fe545 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -75,15 +75,16 @@ trait SyntheticEstimator extends SynapseMLLogging { }) private[causal] def calculateRegularization(data: DataFrame): Double = logVerb("calculateRegularization", { + val diffCol = DatasetExtensions.findUnusedColumnName("diff", data) val Row(firstDiffStd: Double) = data .filter(not(treatment) and not(postTreatment)) .select( (outcome - lag(outcome, 1).over( Window.partitionBy(col(getUnitCol)).orderBy(col(getTimeCol)) - )).as("diff") + )).as(diffCol) ) - .agg(stddev_samp(col("diff"))) + .agg(stddev_samp(col(diffCol))) .head val nTreatedPost = data.filter(treatment and postTreatment).count @@ -116,10 +117,11 @@ trait SyntheticEstimator extends SynapseMLLogging { // "skip", "zero", "impute" getHandleMissingOutcome match { case "skip" => - indexed.withColumn("time_count", count(col(TimeIdxCol)).over(Window.partitionBy(col(UnitIdxCol)))) + val timeCountCol = DatasetExtensions.findUnusedColumnName("time_count", indexed) + indexed.withColumn(timeCountCol, count(col(TimeIdxCol)).over(Window.partitionBy(col(UnitIdxCol)))) // Only skip units from the control_pre group where there is missing data. - .filter(col("time_count") === lit(maxTimeLength) or treatment or postTreatment) - .drop("time_count") + .filter(col(timeCountCol) === lit(maxTimeLength) or treatment or postTreatment) + .drop(timeCountCol) case "zero" => indexed case "impute" => From 43f767fa785f000d9d0bace1c07540b856da8020 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 21:18:53 -0700 Subject: [PATCH 33/41] Expose zeta as an optional parameter, also return the RMSE for unit weights and time weights fitting --- .../synapse/ml/causal/DiffInDiffModel.py | 3 +++ .../ml/causal/BaseDiffInDiffEstimator.scala | 3 +++ .../ml/causal/SyntheticControlEstimator.scala | 3 ++- .../causal/SyntheticDiffInDiffEstimator.scala | 26 +++++++++++++++---- .../ml/causal/SyntheticEstimator.scala | 11 ++++---- .../causal/opt/ConstrainedLeastSquare.scala | 14 ++++++---- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 8d781045ca..45d1575b62 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -39,6 +39,9 @@ def __init__(self, java_obj=None) -> None: self.unitWeights = DiffInDiffModel._mapOption( java_obj.getUnitWeights(), lambda x: DataFrame(x, sql_ctx) ) + self.timeRSME = DiffInDiffModel._unwrapOption(self.summary.timeRMSE()) + self.unitRSME = DiffInDiffModel._unwrapOption(self.summary.unitRSME()) + self.zeta = DiffInDiffModel._unwrapOption(self.summary.zeta()) self.lossHistoryTimeWeights = DiffInDiffModel._unwrapOption( self.summary.getLossHistoryTimeWeightsJava() ) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index e4dcea66d5..cc36bb2e24 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -74,8 +74,11 @@ abstract class BaseDiffInDiffEstimator(override val uid: String) case class DiffInDiffSummary(treatmentEffect: Double, standardError: Double, timeWeights: Option[DVector] = None, timeIntercept: Option[Double] = None, + timeRMSE: Option[Double] = None, unitWeights: Option[DVector] = None, unitIntercept: Option[Double] = None, + unitRMSE: Option[Double] = None, + zeta: Option[Double] = None, lossHistoryTimeWeights: Option[List[Double]] = None, lossHistoryUnitWeights: Option[List[Double]] = None) { import scala.collection.JavaConverters._ diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 5757935d3d..6467d8ed21 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -43,7 +43,7 @@ class SyntheticControlEstimator(override val uid: String) .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) .localCheckpoint(true) - val (unitWeights, unitIntercept, lossHistory) = fitUnitWeights( + val (unitWeights, unitIntercept, unitRMSE, lossHistory) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), zeta = 0d, fitIntercept = false, @@ -96,6 +96,7 @@ class SyntheticControlEstimator(override val uid: String) standardError, unitWeights = Some(unitWeights), unitIntercept = Some(unitIntercept), + unitRMSE = Some(unitRMSE), lossHistoryUnitWeights = Some(lossHistory.toList) ) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index 3ea9a3fbed..f514e76045 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -5,16 +5,29 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable import com.microsoft.azure.synapse.ml.logging.FeatureNames +import org.apache.spark.ml.param.{DoubleParam, ParamValidators, Params} import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.functions._ import org.apache.spark.sql.types.{BooleanType, IntegerType} import org.apache.spark.sql.{Dataset, Row} +trait SyntheticDiffInDiffEstimatorParams extends SyntheticEstimatorParams { + final val zeta = new DoubleParam(this, "zeta", + "The zeta value for regularization term when fitting unit weights. " + + "If not specified, a default value will be computed based on formula (2.2) specified in " + + "https://www.nber.org/system/files/working_papers/w25532/w25532.pdf. " + + "For large scale data, one may want to tune the zeta value, minimizing the loss of the unit weights regression.", + ParamValidators.gtEq(0)) + + def getZeta: Double = $(zeta) + def setZeta(value: Double): this.type = set(zeta, value) +} + class SyntheticDiffInDiffEstimator(override val uid: String) extends BaseDiffInDiffEstimator(uid) with SyntheticEstimator - with SyntheticEstimatorParams + with SyntheticDiffInDiffEstimatorParams with ComplexParamsWritable with Wrappable { @@ -48,15 +61,15 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .localCheckpoint(true) // fit time weights - val (timeWeights, timeIntercept, lossHistoryTimeWeights) = fitTimeWeights( + val (timeWeights, timeIntercept, timeRMSE, lossHistoryTimeWeights) = fitTimeWeights( handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), size ) // fit unit weights - val zeta = calculateRegularization(df) - val (unitWeights, unitIntercept, lossHistoryUnitWeights) = fitUnitWeights( + val zetaValue = this.get(zeta).getOrElse(calculateRegularization(df)) + val (unitWeights, unitIntercept, unitRMSE, lossHistoryUnitWeights) = fitUnitWeights( handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), - zeta, + zetaValue, fitIntercept = true, size.swap ) @@ -108,8 +121,11 @@ class SyntheticDiffInDiffEstimator(override val uid: String) standardError, timeWeights = Some(timeWeights), timeIntercept = Some(timeIntercept), + timeRMSE = Some(timeRMSE), unitWeights = Some(unitWeights), unitIntercept = Some(unitIntercept), + unitRMSE = Some(unitRMSE), + zeta = Some(zetaValue), lossHistoryTimeWeights = Some(lossHistoryTimeWeights.toList), lossHistoryUnitWeights = Some(lossHistoryUnitWeights.toList) ) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index 77cd5fe545..a8a20e41bb 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -29,7 +29,7 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] val findWeightsCol = DatasetExtensions.findUnusedColumnName("weights") _ private def solveCLS(A: DMatrix, b: DVector, lambda: Double, fitIntercept: Boolean, size: (Long, Long)) - : (DVector, Double, Seq[Double]) = { + : (DVector, Double, Double, Seq[Double]) = { if (size._1 * size._2 <= getLocalSolverThreshold) { // If matrix size is less than LocalSolverThreshold (defaults to 1M), // collect the data on the driver node and solve it locally, where matrix-vector @@ -45,9 +45,9 @@ trait SyntheticEstimator extends SynapseMLLogging { numIterNoChange = get(numIterNoChange), tol = this.getTol ) - val (x, intercept, lossHistory) = solver.solve(bzA, bzb, lambda, fitIntercept) + val (x, intercept, rmse, lossHistory) = solver.solve(bzA, bzb, lambda, fitIntercept) val xdf = A.sparkSession.createDataset[VectorEntry](x.mapPairs((i, v) => VectorEntry(i, v)).toArray.toSeq) - (xdf, intercept, lossHistory) + (xdf, intercept, rmse, lossHistory) } else { implicit val cacheOps: CacheOps[DVector] = DVectorCacheOps val solver = new ConstrainedLeastSquare[DMatrix, DVector]( @@ -59,7 +59,8 @@ trait SyntheticEstimator extends SynapseMLLogging { } } - private[causal] def fitTimeWeights(indexedControlDf: DataFrame, size: (Long, Long)): (DVector, Double, Seq[Double]) = + private[causal] def fitTimeWeights(indexedControlDf: DataFrame, size: (Long, Long)) + : (DVector, Double, Double, Seq[Double]) = logVerb("fitTimeWeights", { val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache @@ -95,7 +96,7 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] def fitUnitWeights(indexedPreDf: DataFrame, zeta: Double, fitIntercept: Boolean, - size: (Long, Long)): (DVector, Double, Seq[Double]) = + size: (Long, Long)): (DVector, Double, Double, Seq[Double]) = logVerb("fitUnitWeights", { val outcomePreControl = indexedPreDf.filter(not(treatment)) .toDMatrix(TimeIdxCol, UnitIdxCol, getOutcomeCol) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala index 7ceb8e9a78..0d149093cf 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/opt/ConstrainedLeastSquare.scala @@ -58,25 +58,29 @@ private[causal] class ConstrainedLeastSquare[TMat, TVec](step: Double, def solve(A: TMat, b: TVec, lambda: Double = 0d, - fitIntercept: Boolean = false): (TVec, Double, Seq[Double]) = { + fitIntercept: Boolean = false): (TVec, Double, Double, Seq[Double]) = { val aCentered = if (fitIntercept) matrixOps.centerColumns(A) else A val bCentered = if (fitIntercept) vectorOps.center(b) else b - val xSize = matrixOps.size(aCentered)._2 + val (m, n) = matrixOps.size(aCentered) val lossFunc = getLossFunc(aCentered, bCentered, lambda) val md = new MirrorDescent[TVec](lossFunc, step, maxIter, numIterNoChange, tol) - val init = vectorOps.make(xSize, 1d / xSize) + val init = vectorOps.make(n, 1d / n) val x = md.solve(init) + val lossHistory = md.history.map(_.valueAt) + + val rmse = vectorOps.nrm2(matrixOps.gemv(A, x, Some(b), beta = -1)) / math.sqrt(m) if (fitIntercept){ val colMean = matrixOps.colMean(A) val bMean = vectorOps.mean(b) - (x, bMean - vectorOps.dot(x, colMean), md.history.map(_.valueAt)) + val intercept = bMean - vectorOps.dot(x, colMean) + (x, intercept, rmse, lossHistory) } else { - (x, 0d, md.history.map(_.valueAt)) + (x, 0d, rmse, lossHistory) } } } From 394936e24479920a396c37f42157a83c4711c798 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Oct 2023 22:01:33 -0700 Subject: [PATCH 34/41] Replace constant TimeIdxCol and UnitIdxCol with findUnusedColumn Signed-off-by: Jason Wang --- .../ml/causal/BaseDiffInDiffEstimator.scala | 14 +++++-- .../ml/causal/SyntheticControlEstimator.scala | 21 ++++++---- .../causal/SyntheticDiffInDiffEstimator.scala | 30 ++++++++------ .../ml/causal/SyntheticEstimator.scala | 41 ++++++++++--------- .../ml/causal/VerifySyntheticEstimator.scala | 9 ++-- 5 files changed, 70 insertions(+), 45 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index cc36bb2e24..4b37cab28b 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -10,7 +10,7 @@ import com.microsoft.azure.synapse.ml.logging.{FeatureNames, SynapseMLLogging} import com.microsoft.azure.synapse.ml.param.DataFrameParam import org.apache.spark.SparkException import org.apache.spark.ml.feature.VectorAssembler -import org.apache.spark.ml.param.{ParamMap, Params} +import org.apache.spark.ml.param.{Param, ParamMap, Params} import org.apache.spark.ml.regression.LinearRegression import org.apache.spark.ml.util.Identifiable import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable, Estimator, Model} @@ -106,10 +106,18 @@ class DiffInDiffModel(override val uid: String) def getTimeIndex: DataFrame = $(timeIndex) def setTimeIndex(value: DataFrame): this.type = set(timeIndex, value) + final val timeIndexCol = new Param[String](this, "timeIndexCol", "time index column") + def getTimeIndexCol: String = $(timeIndexCol) + def setTimeIndexCol(value: String): this.type = set(timeIndexCol, value) + final val unitIndex = new DataFrameParam(this, "unitIndex", "unit index") def getUnitIndex: DataFrame = $(unitIndex) def setUnitIndex(value: DataFrame): this.type = set(unitIndex, value) + final val unitIndexCol = new Param[String](this, "unitIndexCol", "unit index column") + def getUnitIndexCol: String = $(unitIndexCol) + def setUnitIndexCol(value: String): this.type = set(unitIndexCol, value) + override protected lazy val pyInternalWrapper = true def this() = this(Identifiable.randomUID("did")) @@ -140,7 +148,7 @@ class DiffInDiffModel(override val uid: String) (get(timeIndex), getSummary.timeWeights) match { case (Some(idxDf), Some(timeWeights)) => Some( - idxDf.join(timeWeights, idxDf(SyntheticEstimator.TimeIdxCol) === timeWeights("i"), "left_outer") + idxDf.join(timeWeights, idxDf(getTimeIndexCol) === timeWeights("i"), "left_outer") .select( idxDf(getTimeCol), timeWeights("value") @@ -155,7 +163,7 @@ class DiffInDiffModel(override val uid: String) (get(unitIndex), getSummary.unitWeights) match { case (Some(idxDf), Some(unitWeights)) => Some( - idxDf.join(unitWeights, idxDf(SyntheticEstimator.UnitIdxCol) === unitWeights("i"), "left_outer") + idxDf.join(unitWeights, idxDf(getUnitIndexCol) === unitWeights("i"), "left_outer") .select( idxDf(getUnitCol), unitWeights("value") diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala index 6467d8ed21..9ce18ffa09 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticControlEstimator.scala @@ -4,6 +4,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.core.schema.DatasetExtensions import com.microsoft.azure.synapse.ml.logging.FeatureNames import org.apache.spark.ml.feature.{OneHotEncoder, StringIndexer} import org.apache.spark.ml.util.Identifiable @@ -33,22 +34,25 @@ class SyntheticControlEstimator(override val uid: String) .toDF val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache - val timeIdx = createIndex(preDf, getTimeCol, TimeIdxCol).cache - val unitIdx = createIndex(controlDf, getUnitCol, UnitIdxCol).cache + + val timeIdxCol = DatasetExtensions.findUnusedColumnName("Time_idx", df) + val unitIdxCol = DatasetExtensions.findUnusedColumnName("Unit_idx", df) + val timeIdx = createIndex(preDf, getTimeCol, timeIdxCol).cache + val unitIdx = createIndex(controlDf, getUnitCol, unitIdxCol).cache val size = (timeIdx.count, unitIdx.count) // indexing val indexedPreDf = preDf.join(timeIdx, preDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") .join(unitIdx, preDf(getUnitCol) === unitIdx(getUnitCol), "left_outer") - .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) + .select(unitIdxCol, timeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) .localCheckpoint(true) val (unitWeights, unitIntercept, unitRMSE, lossHistory) = fitUnitWeights( - handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), + handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt)(unitIdxCol, timeIdxCol), zeta = 0d, fitIntercept = false, size - ) + )(unitIdxCol, timeIdxCol) // join weights val Row(u: Long) = df.agg( @@ -61,12 +65,12 @@ class SyntheticControlEstimator(override val uid: String) val weightsCol = findWeightsCol(indexedDf.columns.toSet) val didData = indexedDf.select( col(getTimeCol), - col(UnitIdxCol), + col(unitIdxCol), postTreatment.cast(IntegerType).as(getPostTreatmentCol), treatment.cast(IntegerType).as(getTreatmentCol), outcome ).as("l") - .join(unitWeights.as("u"), col(s"l.$UnitIdxCol") === col("u.i"), "left_outer") + .join(unitWeights.as("u"), col(s"l.$unitIdxCol") === col("u.i"), "left_outer") .select( col(getTimeCol), postTreatment, @@ -104,7 +108,10 @@ class SyntheticControlEstimator(override val uid: String) .setSummary(Some(summary)) .setParent(this) .setTimeIndex(timeIdx) + .setTimeIndexCol(timeIdxCol) .setUnitIndex(unitIdx) + .setUnitIndexCol(unitIdxCol) + }, dataset.columns.length) private def encodeTimeEffect(didData: DataFrame): DataFrame = { diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala index f514e76045..43c1617702 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticDiffInDiffEstimator.scala @@ -4,6 +4,7 @@ package com.microsoft.azure.synapse.ml.causal import com.microsoft.azure.synapse.ml.codegen.Wrappable +import com.microsoft.azure.synapse.ml.core.schema.DatasetExtensions import com.microsoft.azure.synapse.ml.logging.FeatureNames import org.apache.spark.ml.param.{DoubleParam, ParamValidators, Params} import org.apache.spark.ml.{ComplexParamsReadable, ComplexParamsWritable} @@ -45,34 +46,37 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .toDF val controlDf = df.filter(not(treatment)).cache val preDf = df.filter(not(postTreatment)).cache - val timeIdx = createIndex(preDf, getTimeCol, TimeIdxCol).cache - val unitIdx = createIndex(controlDf, getUnitCol, UnitIdxCol).cache + val timeIdxCol = DatasetExtensions.findUnusedColumnName("Time_idx", df) + val unitIdxCol = DatasetExtensions.findUnusedColumnName("Unit_idx", df) + val timeIdx = createIndex(preDf, getTimeCol, timeIdxCol).cache + val unitIdx = createIndex(controlDf, getUnitCol, unitIdxCol).cache val size = (unitIdx.count, timeIdx.count) // indexing val indexedControlDf = controlDf.join(timeIdx, controlDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") .join(unitIdx, controlDf(getUnitCol) === unitIdx(getUnitCol), "left_outer") - .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) + .select(unitIdxCol, timeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) .localCheckpoint(true) val indexedPreDf = preDf.join(timeIdx, preDf(getTimeCol) === timeIdx(getTimeCol), "left_outer") .join(unitIdx, preDf(getUnitCol) === unitIdx(getUnitCol), "left_outer") - .select(UnitIdxCol, TimeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) + .select(unitIdxCol, timeIdxCol, getTreatmentCol, getPostTreatmentCol, getOutcomeCol) .localCheckpoint(true) // fit time weights val (timeWeights, timeIntercept, timeRMSE, lossHistoryTimeWeights) = fitTimeWeights( - handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt), size - ) + handleMissingOutcomes(indexedControlDf, timeIdx.count.toInt)(unitIdxCol, timeIdxCol), + size + )(unitIdxCol, timeIdxCol) // fit unit weights val zetaValue = this.get(zeta).getOrElse(calculateRegularization(df)) val (unitWeights, unitIntercept, unitRMSE, lossHistoryUnitWeights) = fitUnitWeights( - handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt), + handleMissingOutcomes(indexedPreDf, timeIdx.count.toInt)(unitIdxCol, timeIdxCol), zetaValue, fitIntercept = true, size.swap - ) + )(unitIdxCol, timeIdxCol) // join weights val Row(t: Long, u: Long) = df.agg( @@ -86,14 +90,14 @@ class SyntheticDiffInDiffEstimator(override val uid: String) val interactionCol = findInteractionCol(indexedDf.columns.toSet) val weightsCol = findWeightsCol(indexedDf.columns.toSet) val didData = indexedDf.select( - col(UnitIdxCol), - col(TimeIdxCol), + col(unitIdxCol), + col(timeIdxCol), postTreatment.cast(IntegerType).as(getPostTreatmentCol), treatment.cast(IntegerType).as(getTreatmentCol), outcome ).as("l") - .join(timeWeights.as("t"), col(s"l.$TimeIdxCol") === col("t.i"), "left_outer") - .join(unitWeights.as("u"), col(s"l.$UnitIdxCol") === col("u.i"), "left_outer") + .join(timeWeights.as("t"), col(s"l.$timeIdxCol") === col("t.i"), "left_outer") + .join(unitWeights.as("u"), col(s"l.$unitIdxCol") === col("u.i"), "left_outer") .select( postTreatment, treatment, @@ -134,7 +138,9 @@ class SyntheticDiffInDiffEstimator(override val uid: String) .setSummary(Some(summary)) .setParent(this) .setTimeIndex(timeIdx) + .setTimeIndexCol(timeIdxCol) .setUnitIndex(unitIdx) + .setUnitIndexCol(unitIdxCol) }, dataset.columns.length) } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala index a8a20e41bb..06fbbe2041 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SyntheticEstimator.scala @@ -60,15 +60,16 @@ trait SyntheticEstimator extends SynapseMLLogging { } private[causal] def fitTimeWeights(indexedControlDf: DataFrame, size: (Long, Long)) + (unitIdxCol: String, timeIdxCol: String) : (DVector, Double, Double, Seq[Double]) = logVerb("fitTimeWeights", { val indexedPreControl = indexedControlDf.filter(not(postTreatment)).cache val outcomePre = indexedPreControl - .toDMatrix(UnitIdxCol, TimeIdxCol, getOutcomeCol) + .toDMatrix(unitIdxCol, timeIdxCol, getOutcomeCol) val outcomePostMean = indexedControlDf.filter(postTreatment) - .groupBy(col(UnitIdxCol).as("i")) + .groupBy(col(unitIdxCol).as("i")) .agg(avg(col(getOutcomeCol)).as("value")) .as[VectorEntry] @@ -96,13 +97,14 @@ trait SyntheticEstimator extends SynapseMLLogging { private[causal] def fitUnitWeights(indexedPreDf: DataFrame, zeta: Double, fitIntercept: Boolean, - size: (Long, Long)): (DVector, Double, Double, Seq[Double]) = + size: (Long, Long)) + (unitIdxCol: String, timeIdxCol: String): (DVector, Double, Double, Seq[Double]) = logVerb("fitUnitWeights", { val outcomePreControl = indexedPreDf.filter(not(treatment)) - .toDMatrix(TimeIdxCol, UnitIdxCol, getOutcomeCol) + .toDMatrix(timeIdxCol, unitIdxCol, getOutcomeCol) val outcomePreTreatMean = indexedPreDf.filter(treatment) - .groupBy(col(TimeIdxCol).as("i")) + .groupBy(col(timeIdxCol).as("i")) .agg(avg(outcome).as("value")) .as[VectorEntry] @@ -114,12 +116,13 @@ trait SyntheticEstimator extends SynapseMLLogging { solveCLS(outcomePreControl, outcomePreTreatMean, lambda, fitIntercept, size) }) - private[causal] def handleMissingOutcomes(indexed: DataFrame, maxTimeLength: Int): DataFrame = { + private[causal] def handleMissingOutcomes(indexed: DataFrame, maxTimeLength: Int) + (unitIdxCol: String, timeIdxCol: String): DataFrame = { // "skip", "zero", "impute" getHandleMissingOutcome match { case "skip" => val timeCountCol = DatasetExtensions.findUnusedColumnName("time_count", indexed) - indexed.withColumn(timeCountCol, count(col(TimeIdxCol)).over(Window.partitionBy(col(UnitIdxCol)))) + indexed.withColumn(timeCountCol, count(col(timeIdxCol)).over(Window.partitionBy(col(unitIdxCol)))) // Only skip units from the control_pre group where there is missing data. .filter(col(timeCountCol) === lit(maxTimeLength) or treatment or postTreatment) .drop(timeCountCol) @@ -129,17 +132,17 @@ trait SyntheticEstimator extends SynapseMLLogging { // Only impute the control_pre group. val controlPre = indexed.filter(not(treatment) and not(postTreatment)) - val imputed = imputeTimeSeries(controlPre, maxTimeLength, getOutcomeCol) + val imputed = imputeTimeSeries(controlPre, maxTimeLength, getOutcomeCol, unitIdxCol, timeIdxCol) .withColumn(getTreatmentCol, lit(false)) .withColumn(getPostTreatmentCol, lit(false)) indexed.as("l").join( imputed.as("r"), - col(s"l.$UnitIdxCol") === col(s"r.$UnitIdxCol") and col(s"l.$TimeIdxCol") === col(s"r.$TimeIdxCol"), + col(s"l.$unitIdxCol") === col(s"r.$unitIdxCol") and col(s"l.$timeIdxCol") === col(s"r.$timeIdxCol"), "full_outer" ).select( - coalesce(col(s"l.$UnitIdxCol"), col(s"r.$UnitIdxCol")).as(UnitIdxCol), - coalesce(col(s"l.$TimeIdxCol"), col(s"r.$TimeIdxCol")).as(TimeIdxCol), + coalesce(col(s"l.$unitIdxCol"), col(s"r.$unitIdxCol")).as(unitIdxCol), + coalesce(col(s"l.$timeIdxCol"), col(s"r.$timeIdxCol")).as(timeIdxCol), coalesce(col(s"l.$getOutcomeCol"), col(s"r.$getOutcomeCol")).as(getOutcomeCol), coalesce(col(s"l.$getTreatmentCol"), col(s"r.$getTreatmentCol")).as(getTreatmentCol), coalesce(col(s"l.$getPostTreatmentCol"), col(s"r.$getPostTreatmentCol")).as(getPostTreatmentCol) @@ -149,23 +152,21 @@ trait SyntheticEstimator extends SynapseMLLogging { } object SyntheticEstimator { - val UnitIdxCol = "Unit_idx" - val TimeIdxCol = "Time_idx" - - private[causal] def imputeTimeSeries(df: DataFrame, maxTimeLength: Int, outcomeCol: String): DataFrame = { + private[causal] def imputeTimeSeries(df: DataFrame, maxTimeLength: Int, + outcomeCol: String, unitIdxCol: String, timeIdxCol: String): DataFrame = { val impute: UserDefinedFunction = udf(imputeMissingValues(maxTimeLength) _) df // zip time and outcomes - .select(col(UnitIdxCol), struct(col(TimeIdxCol), col(outcomeCol)).as(outcomeCol)) - .groupBy(UnitIdxCol) + .select(col(unitIdxCol), struct(col(timeIdxCol), col(outcomeCol)).as(outcomeCol)) + .groupBy(unitIdxCol) // construct a map of time -> outcome per unit .agg(map_from_entries(collect_set(col(outcomeCol))).as(outcomeCol)) // impute and explode back - .select(col(UnitIdxCol), explode(impute(col(outcomeCol))).as("exploded")) + .select(col(unitIdxCol), explode(impute(col(outcomeCol))).as("exploded")) .select( - col(UnitIdxCol), - col("exploded._1").as(TimeIdxCol), + col(unitIdxCol), + col("exploded._1").as(timeIdxCol), col("exploded._2").as(outcomeCol) ) } diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala index ae4f59e83a..52a6a999de 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala @@ -10,14 +10,17 @@ import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} class VerifySyntheticEstimator extends TestBase { import spark.implicits._ + private val UnitIdxCol = "Unit_Idx" + private val TimeIdxCol = "Time_Idx" + test("imputeTimeSeries") { val df = Seq( (0, 3, 1.0), (0, 5, 2.0), (0, 8, 5.0) - ) toDF (SyntheticEstimator.UnitIdxCol, SyntheticEstimator.TimeIdxCol, "Outcome") + ) toDF (UnitIdxCol, TimeIdxCol, "Outcome") - val result = SyntheticEstimator.imputeTimeSeries(df, 10, "Outcome") + val result = SyntheticEstimator.imputeTimeSeries(df, 10, "Outcome", UnitIdxCol, TimeIdxCol) val expected = Seq( (0, 0, 1.0), @@ -30,7 +33,7 @@ class VerifySyntheticEstimator extends TestBase { (0, 7, 3.5), (0, 8, 5.0), (0, 9, 5.0) - ) toDF (SyntheticEstimator.UnitIdxCol, SyntheticEstimator.TimeIdxCol, "Outcome") + ) toDF (UnitIdxCol, TimeIdxCol, "Outcome") super.verifyResult(expected, result) } From be581ad5eb9549a0ea9b92ed9a0ce20a803882a2 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 31 Oct 2023 12:50:20 -0700 Subject: [PATCH 35/41] typo --- .../main/python/synapse/ml/causal/DiffInDiffModel.py | 4 ++-- .../synapse/ml/causal/VerifySyntheticEstimator.scala | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py index 45d1575b62..f4d91dee50 100644 --- a/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py +++ b/core/src/main/python/synapse/ml/causal/DiffInDiffModel.py @@ -39,8 +39,8 @@ def __init__(self, java_obj=None) -> None: self.unitWeights = DiffInDiffModel._mapOption( java_obj.getUnitWeights(), lambda x: DataFrame(x, sql_ctx) ) - self.timeRSME = DiffInDiffModel._unwrapOption(self.summary.timeRMSE()) - self.unitRSME = DiffInDiffModel._unwrapOption(self.summary.unitRSME()) + self.timeRMSE = DiffInDiffModel._unwrapOption(self.summary.timeRMSE()) + self.unitRMSE = DiffInDiffModel._unwrapOption(self.summary.unitRMSE()) self.zeta = DiffInDiffModel._unwrapOption(self.summary.zeta()) self.lossHistoryTimeWeights = DiffInDiffModel._unwrapOption( self.summary.getLossHistoryTimeWeightsJava() diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala index 52a6a999de..9dc3d9188e 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticEstimator.scala @@ -10,17 +10,17 @@ import breeze.linalg.{DenseMatrix => BDM, DenseVector => BDV} class VerifySyntheticEstimator extends TestBase { import spark.implicits._ - private val UnitIdxCol = "Unit_Idx" - private val TimeIdxCol = "Time_Idx" + private val unitIdxCol = "Unit_Idx" + private val timeIdxCol = "Time_Idx" test("imputeTimeSeries") { val df = Seq( (0, 3, 1.0), (0, 5, 2.0), (0, 8, 5.0) - ) toDF (UnitIdxCol, TimeIdxCol, "Outcome") + ) toDF (unitIdxCol, timeIdxCol, "Outcome") - val result = SyntheticEstimator.imputeTimeSeries(df, 10, "Outcome", UnitIdxCol, TimeIdxCol) + val result = SyntheticEstimator.imputeTimeSeries(df, 10, "Outcome", unitIdxCol, timeIdxCol) val expected = Seq( (0, 0, 1.0), @@ -33,7 +33,7 @@ class VerifySyntheticEstimator extends TestBase { (0, 7, 3.5), (0, 8, 5.0), (0, 9, 5.0) - ) toDF (UnitIdxCol, TimeIdxCol, "Outcome") + ) toDF (unitIdxCol, timeIdxCol, "Outcome") super.verifyResult(expected, result) } From a3af20efb737200acfe31dca110383ee88fae410 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 31 Oct 2023 13:59:10 -0700 Subject: [PATCH 36/41] Adding notebook to sidebar --- website/sidebars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/website/sidebars.js b/website/sidebars.js index c3ea247681..88eada0254 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -90,6 +90,7 @@ module.exports = { "Explore Algorithms/Causal Inference/Overview", "Explore Algorithms/Causal Inference/Quickstart - Measure Causal Effects", "Explore Algorithms/Causal Inference/Quickstart - Measure Heterogeneous Effects", + "Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences", ], }, From 5b9ab17d0c5be2bb27951dc64ea9daf764eb3f55 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 2 Nov 2023 20:45:42 -0700 Subject: [PATCH 37/41] fix bad merge --- .../azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala b/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala index 7951c9f766..802f664a22 100644 --- a/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala +++ b/src/test/scala/com/microsoft/azure/synapse/ml/core/test/fuzzing/FuzzingTest.scala @@ -44,7 +44,6 @@ class FuzzingTest extends TestBase { test("Verify stage fitting and transforming") { val exemptions: Set[String] = Set( - "com.microsoft.azure.synapse.ml.services.text.TextAnalyze", "com.microsoft.azure.synapse.ml.services.text.TextAnalyze", "com.microsoft.azure.synapse.ml.causal.DoubleMLModel", "com.microsoft.azure.synapse.ml.causal.DiffInDiffModel", @@ -102,8 +101,8 @@ class FuzzingTest extends TestBase { test("Verify all stages can be serialized") { val exemptions: Set[String] = Set( - "com.microsoft.azure.synapse.ml.cognitive.text.TextAnalyze", - "com.microsoft.azure.synapse.ml.cognitive.translate.DocumentTranslator", + "com.microsoft.azure.synapse.ml.services.text.TextAnalyze", + "com.microsoft.azure.synapse.ml.services.translate.DocumentTranslator", "com.microsoft.azure.synapse.ml.causal.DiffInDiffModel", "com.microsoft.azure.synapse.ml.automl.BestModel", "com.microsoft.azure.synapse.ml.automl.TuneHyperparameters", From 666d1ed287bb0cbb4b968b71f741d0844a8ad869 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 18 Dec 2023 12:59:53 -0800 Subject: [PATCH 38/41] address code review comments --- .../ml/causal/BaseDiffInDiffEstimator.scala | 4 +-- .../synapse/ml/causal/SharedParams.scala | 6 ++-- .../VerifySyntheticDiffInDiffEstimator.scala | 36 ++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala index 4b37cab28b..a044175db8 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/BaseDiffInDiffEstimator.scala @@ -120,7 +120,7 @@ class DiffInDiffModel(override val uid: String) override protected lazy val pyInternalWrapper = true - def this() = this(Identifiable.randomUID("did")) + def this() = this(Identifiable.randomUID("DiffInDiffModel")) private final var summary: Option[DiffInDiffSummary] = None @@ -135,7 +135,7 @@ class DiffInDiffModel(override val uid: String) } override def copy(extra: ParamMap): DiffInDiffModel = { - copyValues(new DiffInDiffModel(uid)) + copyValues(new DiffInDiffModel(uid), extra) .setSummary(this.summary) .setParent(parent) } diff --git a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala index bed393e2de..82972f8225 100644 --- a/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala +++ b/core/src/main/scala/com/microsoft/azure/synapse/ml/causal/SharedParams.scala @@ -43,14 +43,16 @@ trait HasPostTreatmentCol extends Params { trait HasUnitCol extends Params { final val unitCol = new Param[String](this, "unitCol", - "Column that identifies the units in panel data") + "Specify the name of the column which contains an identifier for each observed unit in the panel data. " + + "For example, if the observed units are users, this column could be the UserId column.") def getUnitCol: String = $(unitCol) def setUnitCol(value: String): this.type = set(unitCol, value) } trait HasTimeCol extends Params { final val timeCol = new Param[String](this, "timeCol", - "Column that identifies the time when outcome is measured in panel data") + "Specify the column that identifies the time when outcome is measured in the panel data. " + + "For example, if the outcome is measured daily, this column could be the Date column.") def getTimeCol: String = $(timeCol) def setTimeCol(value: String): this.type = set(timeCol, value) } diff --git a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala index 415baeee1d..da13314f17 100644 --- a/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala +++ b/core/src/test/scala/com/microsoft/azure/synapse/ml/causal/VerifySyntheticDiffInDiffEstimator.scala @@ -10,6 +10,7 @@ import org.apache.log4j.{Level, Logger} import org.apache.spark.ml.util.MLReadable import org.scalactic.{Equality, TolerantNumerics} import com.microsoft.azure.synapse.ml.causal.linalg._ +import org.apache.spark.ml.param.ParamMap class VerifySyntheticDiffInDiffEstimator extends EstimatorFuzzing[SyntheticDiffInDiffEstimator] { @@ -72,11 +73,8 @@ class VerifySyntheticDiffInDiffEstimator .setUnitCol("Unit") .setTimeCol("Time") .setMaxIter(500) - // Set LocalSolverThreshold to 1 to force Spark mode - // Spark mode and breeze mode should get same loss history and same solution - // .setLocalSolverThreshold(1) - test("SyntheticDiffInDiffEstimator can estimate the treatment effect") { + test("SyntheticDiffInDiffEstimator can estimate the treatment effect in local mode") { implicit val vectorOps: VectorOps[DVector] = DVectorOps val summary = estimator.fit(df).getSummary @@ -97,6 +95,36 @@ class VerifySyntheticDiffInDiffEstimator assert(summary.standardError === 0.30221430259614196) } + test("SyntheticDiffInDiffEstimator can estimate the treatment effect in distributed mode") { + // Set LocalSolverThreshold to 1 to force Spark mode + val distributedEstimator = estimator.copy(ParamMap.empty) + .asInstanceOf[SyntheticDiffInDiffEstimator] + .setLocalSolverThreshold(1) + // Set maxIter to smaller value to reduce unit test time. Result will be slightly different than local mode. + .setMaxIter(20) + + implicit val vectorOps: VectorOps[DVector] = DVectorOps + + val summary = distributedEstimator.fit(df).getSummary + assert(summary.timeIntercept.get === 4.924434349260821) + + val timeWeights = summary.timeWeights.get.toBreeze + assert(sum(timeWeights) === 1.0) + assert(timeWeights.size === 10) + assert(timeWeights.forall(0 <= _ && _ <= 1)) + + assert(summary.unitIntercept.get === -54.83322195267364) + val unitWeights = summary.unitWeights.get.toBreeze + + assert(sum(unitWeights) === 1.0) + assert(unitWeights.size === 100) + assert(unitWeights.forall(0 <= _ && _ <= 1)) + + // The values slightly differ from local mode since we're running less iterations to save time. + assert(summary.treatmentEffect === -14.886230487053334) + assert(summary.standardError === 0.27395418857682896) + } + override def testObjects(): Seq[TestObject[SyntheticDiffInDiffEstimator]] = Seq( new TestObject(estimator, df, df) ) From 5cc90277b27af38e77d00dbea5d214ee2e154e9f Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 18 Dec 2023 13:08:40 -0800 Subject: [PATCH 39/41] Update docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb Co-authored-by: Mark Hamilton --- .../Quickstart - Synthetic difference in differences.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb index 08a6cf3095..6f8c46f01a 100644 --- a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb +++ b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb @@ -18,7 +18,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this sample notebook, we will use the California smoking cessation program example to demonstrate the usage. The goal of the analysis is to estimate the effect of increased cigarette taxes on smoking in California." + "In this sample notebook, we will use the California smoking cessation program example to demonstrate usage of the SyntheticDiffInDiff Estimator. The goal of the analysis is to estimate the effect of increased cigarette taxes on smoking in California." ] }, { From f9bb83cbc2237a9f7b3a24b08858db3f5390a211 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 18 Dec 2023 13:21:44 -0800 Subject: [PATCH 40/41] clean synapse widget output state --- ... Synthetic difference in differences.ipynb | 7059 +---------------- 1 file changed, 1 insertion(+), 7058 deletions(-) diff --git a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb index 6f8c46f01a..7fedefa09d 100644 --- a/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb +++ b/docs/Explore Algorithms/Causal Inference/Quickstart - Synthetic difference in differences.ipynb @@ -467,7064 +467,7 @@ }, "save_output": true, "synapse_widget": { - "state": { - "7cfbe0f7-9f3c-44c3-828d-922a7bff621e": { - "persist_state": { - "view": { - "chartOptions": { - "aggregationType": "sum", - "categoryFieldKeys": [ - "1" - ], - "chartType": "bar", - "isStacked": false, - "seriesFieldKeys": [ - "0" - ] - }, - "tableOptions": {}, - "type": "details" - } - }, - "sync_state": { - "isSummary": false, - "language": "scala", - "table": { - "rows": [ - { - "0": "1", - "1": "1970", - "2": "89.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1971", - "2": "95.4000015258789", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1972", - "2": "101.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1973", - "2": "102.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1974", - "2": "108.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1975", - "2": "111.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1976", - "2": "116.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1977", - "2": "117.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1978", - "2": "123.0", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1979", - "2": "121.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1980", - "2": "123.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1981", - "2": "119.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1982", - "2": "119.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1983", - "2": "116.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1984", - "2": "113.0", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1985", - "2": "114.5", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1986", - "2": "116.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1987", - "2": "114.0", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1988", - "2": "112.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "1", - "1": "1989", - "2": "105.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1990", - "2": "108.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1991", - "2": "107.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1992", - "2": "109.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1993", - "2": "108.5", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1994", - "2": "107.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1995", - "2": "102.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1996", - "2": "101.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1997", - "2": "104.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1998", - "2": "106.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "1999", - "2": "100.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "1", - "1": "2000", - "2": "96.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1970", - "2": "100.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1971", - "2": "104.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1972", - "2": "103.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1973", - "2": "108.0", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1974", - "2": "109.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1975", - "2": "114.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1976", - "2": "119.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1977", - "2": "122.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1978", - "2": "127.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1979", - "2": "126.5", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1980", - "2": "131.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1981", - "2": "128.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1982", - "2": "127.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1983", - "2": "128.0", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1984", - "2": "123.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1985", - "2": "125.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1986", - "2": "126.0", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1987", - "2": "122.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1988", - "2": "121.5", - "3": "false", - "4": "false" - }, - { - "0": "2", - "1": "1989", - "2": "118.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1990", - "2": "113.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1991", - "2": "116.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1992", - "2": "126.0", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1993", - "2": "113.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1994", - "2": "108.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1995", - "2": "113.0", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1996", - "2": "110.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1997", - "2": "108.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1998", - "2": "109.5", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "1999", - "2": "104.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "2", - "1": "2000", - "2": "99.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "3", - "1": "1970", - "2": "123.0", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1971", - "2": "121.0", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1972", - "2": "123.5", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1973", - "2": "124.400001525879", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1974", - "2": "126.699996948242", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1975", - "2": "127.099998474121", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1976", - "2": "128.0", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1977", - "2": "126.400001525879", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1978", - "2": "126.099998474121", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1979", - "2": "121.900001525879", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1980", - "2": "120.199996948242", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1981", - "2": "118.599998474121", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1982", - "2": "115.400001525879", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1983", - "2": "110.800003051758", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1984", - "2": "104.800003051758", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1985", - "2": "102.800003051758", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1986", - "2": "99.6999969482422", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1987", - "2": "97.5", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1988", - "2": "90.0999984741211", - "3": "true", - "4": "false" - }, - { - "0": "3", - "1": "1989", - "2": "82.4000015258789", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1990", - "2": "77.8000030517578", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1991", - "2": "68.6999969482422", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1992", - "2": "67.5", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1993", - "2": "63.4000015258789", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1994", - "2": "58.5999984741211", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1995", - "2": "56.4000015258789", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1996", - "2": "54.5", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1997", - "2": "53.7999992370605", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1998", - "2": "52.2999992370605", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "1999", - "2": "47.2000007629395", - "3": "true", - "4": "true" - }, - { - "0": "3", - "1": "2000", - "2": "41.5999984741211", - "3": "true", - "4": "true" - }, - { - "0": "4", - "1": "1970", - "2": "124.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1971", - "2": "125.5", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1972", - "2": "134.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1973", - "2": "137.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1974", - "2": "132.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1975", - "2": "131.0", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1976", - "2": "134.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1977", - "2": "132.0", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1978", - "2": "129.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1979", - "2": "131.5", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1980", - "2": "131.0", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1981", - "2": "133.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1982", - "2": "130.5", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1983", - "2": "125.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1984", - "2": "119.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1985", - "2": "112.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1986", - "2": "109.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1987", - "2": "102.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1988", - "2": "94.5999984741211", - "3": "false", - "4": "false" - }, - { - "0": "4", - "1": "1989", - "2": "88.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1990", - "2": "87.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1991", - "2": "90.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1992", - "2": "88.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1993", - "2": "88.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1994", - "2": "89.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1995", - "2": "85.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1996", - "2": "83.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1997", - "2": "81.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1998", - "2": "81.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "1999", - "2": "79.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "4", - "1": "2000", - "2": "73.0", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1970", - "2": "120.0", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1971", - "2": "117.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1972", - "2": "110.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1973", - "2": "109.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1974", - "2": "112.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1975", - "2": "110.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1976", - "2": "113.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1977", - "2": "117.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1978", - "2": "117.5", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1979", - "2": "117.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1980", - "2": "118.0", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1981", - "2": "116.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1982", - "2": "114.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1983", - "2": "114.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1984", - "2": "112.5", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1985", - "2": "111.0", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1986", - "2": "108.5", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1987", - "2": "109.0", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1988", - "2": "104.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "5", - "1": "1989", - "2": "100.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1990", - "2": "91.5", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1991", - "2": "86.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1992", - "2": "83.5", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1993", - "2": "79.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1994", - "2": "76.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1995", - "2": "79.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1996", - "2": "76.0", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1997", - "2": "75.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1998", - "2": "75.5", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "1999", - "2": "73.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "5", - "1": "2000", - "2": "71.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1970", - "2": "155.0", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1971", - "2": "161.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1972", - "2": "156.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1973", - "2": "154.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1974", - "2": "151.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1975", - "2": "147.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1976", - "2": "153.0", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1977", - "2": "153.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1978", - "2": "155.5", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1979", - "2": "150.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1980", - "2": "150.5", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1981", - "2": "152.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1982", - "2": "154.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1983", - "2": "149.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1984", - "2": "144.0", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1985", - "2": "144.5", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1986", - "2": "142.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1987", - "2": "141.0", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1988", - "2": "137.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "6", - "1": "1989", - "2": "131.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1990", - "2": "127.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1991", - "2": "118.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1992", - "2": "120.0", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1993", - "2": "123.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1994", - "2": "126.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1995", - "2": "127.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1996", - "2": "128.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1997", - "2": "124.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1998", - "2": "132.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "1999", - "2": "139.5", - "3": "false", - "4": "true" - }, - { - "0": "6", - "1": "2000", - "2": "140.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1970", - "2": "109.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1971", - "2": "115.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1972", - "2": "117.0", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1973", - "2": "119.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1974", - "2": "123.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1975", - "2": "122.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1976", - "2": "125.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1977", - "2": "127.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1978", - "2": "130.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1979", - "2": "131.0", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1980", - "2": "134.0", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1981", - "2": "131.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1982", - "2": "131.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1983", - "2": "128.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1984", - "2": "126.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1985", - "2": "128.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1986", - "2": "129.0", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1987", - "2": "129.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1988", - "2": "124.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "7", - "1": "1989", - "2": "117.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1990", - "2": "113.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1991", - "2": "109.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1992", - "2": "109.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1993", - "2": "109.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1994", - "2": "107.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1995", - "2": "100.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1996", - "2": "102.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1997", - "2": "100.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1998", - "2": "100.5", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "1999", - "2": "97.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "7", - "1": "2000", - "2": "88.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1970", - "2": "102.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1971", - "2": "108.5", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1972", - "2": "126.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1973", - "2": "121.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1974", - "2": "125.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1975", - "2": "123.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1976", - "2": "125.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1977", - "2": "125.0", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1978", - "2": "122.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1979", - "2": "117.5", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1980", - "2": "115.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1981", - "2": "114.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1982", - "2": "111.5", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1983", - "2": "111.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1984", - "2": "103.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1985", - "2": "100.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1986", - "2": "96.6999969482422", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1987", - "2": "95.0", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1988", - "2": "84.5", - "3": "false", - "4": "false" - }, - { - "0": "8", - "1": "1989", - "2": "78.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1990", - "2": "90.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1991", - "2": "85.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1992", - "2": "85.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1993", - "2": "86.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1994", - "2": "93.0", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1995", - "2": "78.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1996", - "2": "73.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1997", - "2": "75.0", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1998", - "2": "78.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "1999", - "2": "75.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "8", - "1": "2000", - "2": "66.90000152587889", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1970", - "2": "124.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1971", - "2": "125.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1972", - "2": "126.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1973", - "2": "124.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1974", - "2": "131.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1975", - "2": "131.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1976", - "2": "134.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1977", - "2": "134.0", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1978", - "2": "136.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1979", - "2": "135.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1980", - "2": "135.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1981", - "2": "133.0", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1982", - "2": "130.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1983", - "2": "127.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1984", - "2": "124.0", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1985", - "2": "121.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1986", - "2": "118.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1987", - "2": "109.5", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1988", - "2": "107.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "9", - "1": "1989", - "2": "104.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1990", - "2": "94.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1991", - "2": "96.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1992", - "2": "94.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1993", - "2": "94.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1994", - "2": "85.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1995", - "2": "84.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1996", - "2": "81.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1997", - "2": "79.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1998", - "2": "80.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "1999", - "2": "72.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "9", - "1": "2000", - "2": "70.0", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1970", - "2": "134.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1971", - "2": "139.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1972", - "2": "149.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1973", - "2": "156.0", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1974", - "2": "159.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1975", - "2": "162.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1976", - "2": "166.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1977", - "2": "173.0", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1978", - "2": "150.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1979", - "2": "148.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1980", - "2": "146.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1981", - "2": "148.5", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1982", - "2": "147.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1983", - "2": "143.0", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1984", - "2": "137.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1985", - "2": "135.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1986", - "2": "137.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1987", - "2": "134.0", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1988", - "2": "134.0", - "3": "false", - "4": "false" - }, - { - "0": "10", - "1": "1989", - "2": "132.5", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1990", - "2": "128.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1991", - "2": "127.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1992", - "2": "128.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1993", - "2": "126.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1994", - "2": "128.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1995", - "2": "135.399993896484", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1996", - "2": "135.100006103516", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1997", - "2": "135.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1998", - "2": "135.899993896484", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "1999", - "2": "133.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "10", - "1": "2000", - "2": "125.5", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1970", - "2": "108.5", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1971", - "2": "108.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1972", - "2": "109.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1973", - "2": "110.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1974", - "2": "116.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1975", - "2": "120.5", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1976", - "2": "124.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1977", - "2": "125.5", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1978", - "2": "127.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1979", - "2": "124.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1980", - "2": "124.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1981", - "2": "132.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1982", - "2": "116.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1983", - "2": "115.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1984", - "2": "111.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1985", - "2": "109.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1986", - "2": "104.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1987", - "2": "101.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1988", - "2": "100.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "11", - "1": "1989", - "2": "94.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1990", - "2": "95.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1991", - "2": "97.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1992", - "2": "95.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1993", - "2": "92.5", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1994", - "2": "93.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1995", - "2": "93.0", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1996", - "2": "94.0", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1997", - "2": "93.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1998", - "2": "94.0", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "1999", - "2": "91.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "11", - "1": "2000", - "2": "88.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1970", - "2": "114.0", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1971", - "2": "102.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1972", - "2": "111.0", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1973", - "2": "115.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1974", - "2": "118.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1975", - "2": "123.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1976", - "2": "127.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1977", - "2": "127.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1978", - "2": "127.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1979", - "2": "126.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1980", - "2": "127.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1981", - "2": "132.0", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1982", - "2": "130.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1983", - "2": "127.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1984", - "2": "121.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1985", - "2": "115.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1986", - "2": "109.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1987", - "2": "105.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1988", - "2": "103.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "12", - "1": "1989", - "2": "96.5", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1990", - "2": "94.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1991", - "2": "91.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1992", - "2": "90.0", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1993", - "2": "89.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1994", - "2": "89.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1995", - "2": "90.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1996", - "2": "88.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1997", - "2": "89.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1998", - "2": "87.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "1999", - "2": "83.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "12", - "1": "2000", - "2": "79.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1970", - "2": "155.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1971", - "2": "163.5", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1972", - "2": "179.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1973", - "2": "201.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1974", - "2": "212.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1975", - "2": "223.0", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1976", - "2": "230.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1977", - "2": "229.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1978", - "2": "224.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1979", - "2": "214.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1980", - "2": "215.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1981", - "2": "209.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1982", - "2": "210.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1983", - "2": "201.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1984", - "2": "183.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1985", - "2": "182.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1986", - "2": "179.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1987", - "2": "171.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1988", - "2": "173.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "13", - "1": "1989", - "2": "171.600006103516", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1990", - "2": "182.5", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1991", - "2": "170.399993896484", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1992", - "2": "167.600006103516", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1993", - "2": "167.600006103516", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1994", - "2": "170.100006103516", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1995", - "2": "175.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1996", - "2": "179.0", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1997", - "2": "186.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1998", - "2": "171.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "1999", - "2": "165.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "13", - "1": "2000", - "2": "156.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1970", - "2": "115.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1971", - "2": "119.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1972", - "2": "125.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1973", - "2": "126.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1974", - "2": "129.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1975", - "2": "133.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1976", - "2": "139.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1977", - "2": "140.0", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1978", - "2": "142.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1979", - "2": "140.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1980", - "2": "143.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1981", - "2": "144.0", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1982", - "2": "143.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1983", - "2": "133.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1984", - "2": "128.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1985", - "2": "125.0", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1986", - "2": "121.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1987", - "2": "116.5", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1988", - "2": "110.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "14", - "1": "1989", - "2": "103.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1990", - "2": "101.5", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1991", - "2": "107.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1992", - "2": "108.5", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1993", - "2": "106.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1994", - "2": "105.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1995", - "2": "105.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1996", - "2": "106.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1997", - "2": "105.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1998", - "2": "103.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "1999", - "2": "101.0", - "3": "false", - "4": "true" - }, - { - "0": "14", - "1": "2000", - "2": "104.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1970", - "2": "128.5", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1971", - "2": "133.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1972", - "2": "136.5", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1973", - "2": "138.0", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1974", - "2": "142.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1975", - "2": "140.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1976", - "2": "144.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1977", - "2": "145.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1978", - "2": "143.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1979", - "2": "138.5", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1980", - "2": "141.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1981", - "2": "138.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1982", - "2": "139.5", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1983", - "2": "135.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1984", - "2": "135.5", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1985", - "2": "127.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1986", - "2": "119.0", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1987", - "2": "125.0", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1988", - "2": "125.0", - "3": "false", - "4": "false" - }, - { - "0": "15", - "1": "1989", - "2": "122.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1990", - "2": "117.5", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1991", - "2": "116.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1992", - "2": "114.5", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1993", - "2": "108.5", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1994", - "2": "101.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1995", - "2": "102.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1996", - "2": "100.0", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1997", - "2": "101.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1998", - "2": "94.5", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "1999", - "2": "85.5", - "3": "false", - "4": "true" - }, - { - "0": "15", - "1": "2000", - "2": "82.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1970", - "2": "104.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1971", - "2": "116.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1972", - "2": "96.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1973", - "2": "106.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1974", - "2": "110.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1975", - "2": "111.5", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1976", - "2": "116.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1977", - "2": "117.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1978", - "2": "118.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1979", - "2": "118.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1980", - "2": "117.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1981", - "2": "120.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1982", - "2": "119.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1983", - "2": "113.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1984", - "2": "110.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1985", - "2": "113.0", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1986", - "2": "104.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1987", - "2": "108.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1988", - "2": "94.0999984741211", - "3": "false", - "4": "false" - }, - { - "0": "16", - "1": "1989", - "2": "92.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1990", - "2": "90.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1991", - "2": "86.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1992", - "2": "83.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1993", - "2": "81.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1994", - "2": "83.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1995", - "2": "84.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1996", - "2": "81.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1997", - "2": "84.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1998", - "2": "83.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "1999", - "2": "80.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "16", - "1": "2000", - "2": "76.0", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1970", - "2": "93.4000015258789", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1971", - "2": "105.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1972", - "2": "112.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1973", - "2": "115.0", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1974", - "2": "117.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1975", - "2": "116.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1976", - "2": "120.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1977", - "2": "122.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1978", - "2": "124.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1979", - "2": "123.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1980", - "2": "127.0", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1981", - "2": "125.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1982", - "2": "125.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1983", - "2": "122.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1984", - "2": "116.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1985", - "2": "115.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1986", - "2": "113.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1987", - "2": "110.0", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1988", - "2": "109.0", - "3": "false", - "4": "false" - }, - { - "0": "17", - "1": "1989", - "2": "108.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1990", - "2": "101.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1991", - "2": "105.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1992", - "2": "103.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1993", - "2": "105.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1994", - "2": "106.0", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1995", - "2": "107.5", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1996", - "2": "106.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1997", - "2": "106.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1998", - "2": "107.0", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "1999", - "2": "103.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "17", - "1": "2000", - "2": "97.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1970", - "2": "121.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1971", - "2": "127.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1972", - "2": "130.0", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1973", - "2": "132.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1974", - "2": "135.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1975", - "2": "135.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1976", - "2": "139.5", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1977", - "2": "140.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1978", - "2": "141.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1979", - "2": "140.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1980", - "2": "142.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1981", - "2": "140.5", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1982", - "2": "139.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1983", - "2": "134.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1984", - "2": "130.0", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1985", - "2": "129.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1986", - "2": "128.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1987", - "2": "128.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1988", - "2": "127.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "18", - "1": "1989", - "2": "122.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1990", - "2": "119.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1991", - "2": "119.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1992", - "2": "122.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1993", - "2": "121.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1994", - "2": "119.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1995", - "2": "124.0", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1996", - "2": "124.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1997", - "2": "120.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1998", - "2": "120.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "1999", - "2": "118.0", - "3": "false", - "4": "true" - }, - { - "0": "18", - "1": "2000", - "2": "113.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1970", - "2": "111.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1971", - "2": "115.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1972", - "2": "122.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1973", - "2": "119.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1974", - "2": "121.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1975", - "2": "123.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1976", - "2": "124.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1977", - "2": "127.0", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1978", - "2": "127.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1979", - "2": "120.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1980", - "2": "122.0", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1981", - "2": "121.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1982", - "2": "122.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1983", - "2": "113.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1984", - "2": "110.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1985", - "2": "103.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1986", - "2": "97.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1987", - "2": "91.6999969482422", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1988", - "2": "87.0999984741211", - "3": "false", - "4": "false" - }, - { - "0": "19", - "1": "1989", - "2": "86.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1990", - "2": "84.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1991", - "2": "82.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1992", - "2": "86.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1993", - "2": "86.0", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1994", - "2": "88.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1995", - "2": "90.5", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1996", - "2": "87.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1997", - "2": "88.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1998", - "2": "89.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "1999", - "2": "82.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "19", - "1": "2000", - "2": "75.5", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1970", - "2": "108.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1971", - "2": "108.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1972", - "2": "104.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1973", - "2": "106.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1974", - "2": "110.5", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1975", - "2": "114.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1976", - "2": "118.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1977", - "2": "117.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1978", - "2": "117.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1979", - "2": "116.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1980", - "2": "116.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1981", - "2": "117.0", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1982", - "2": "117.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1983", - "2": "110.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1984", - "2": "107.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1985", - "2": "105.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1986", - "2": "103.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1987", - "2": "101.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1988", - "2": "92.9000015258789", - "3": "false", - "4": "false" - }, - { - "0": "20", - "1": "1989", - "2": "93.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1990", - "2": "89.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1991", - "2": "92.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1992", - "2": "90.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1993", - "2": "91.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1994", - "2": "85.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1995", - "2": "88.5", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1996", - "2": "86.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1997", - "2": "85.5", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1998", - "2": "83.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "1999", - "2": "86.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "20", - "1": "2000", - "2": "77.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1970", - "2": "189.5", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1971", - "2": "190.5", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1972", - "2": "198.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1973", - "2": "201.5", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1974", - "2": "204.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1975", - "2": "205.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1976", - "2": "201.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1977", - "2": "190.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1978", - "2": "187.0", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1979", - "2": "183.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1980", - "2": "177.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1981", - "2": "171.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1982", - "2": "165.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1983", - "2": "159.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1984", - "2": "136.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1985", - "2": "146.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1986", - "2": "142.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1987", - "2": "147.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1988", - "2": "141.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "21", - "1": "1989", - "2": "137.899993896484", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1990", - "2": "137.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1991", - "2": "115.5", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1992", - "2": "110.0", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1993", - "2": "108.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1994", - "2": "105.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1995", - "2": "100.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1996", - "2": "99.0", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1997", - "2": "95.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1998", - "2": "102.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "1999", - "2": "103.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "21", - "1": "2000", - "2": "93.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1970", - "2": "265.70001220703097", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1971", - "2": "278.0", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1972", - "2": "296.20001220703097", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1973", - "2": "279.0", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1974", - "2": "269.79998779296903", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1975", - "2": "269.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1976", - "2": "290.5", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1977", - "2": "278.79998779296903", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1978", - "2": "269.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1979", - "2": "254.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1980", - "2": "247.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1981", - "2": "245.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1982", - "2": "239.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1983", - "2": "232.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1984", - "2": "215.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1985", - "2": "201.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1986", - "2": "195.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1987", - "2": "195.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1988", - "2": "180.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "22", - "1": "1989", - "2": "172.899993896484", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1990", - "2": "152.399993896484", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1991", - "2": "144.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1992", - "2": "143.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1993", - "2": "148.899993896484", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1994", - "2": "153.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1995", - "2": "158.5", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1996", - "2": "158.0", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1997", - "2": "174.399993896484", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1998", - "2": "173.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "1999", - "2": "171.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "22", - "1": "2000", - "2": "147.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1970", - "2": "90.0", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1971", - "2": "92.5999984741211", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1972", - "2": "99.3000030517578", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1973", - "2": "98.9000015258789", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1974", - "2": "100.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1975", - "2": "103.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1976", - "2": "102.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1977", - "2": "102.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1978", - "2": "103.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1979", - "2": "101.0", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1980", - "2": "102.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1981", - "2": "103.0", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1982", - "2": "97.5", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1983", - "2": "96.3000030517578", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1984", - "2": "88.9000015258789", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1985", - "2": "88.0", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1986", - "2": "88.1999969482422", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1987", - "2": "82.3000030517578", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1988", - "2": "77.6999969482422", - "3": "false", - "4": "false" - }, - { - "0": "23", - "1": "1989", - "2": "74.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1990", - "2": "70.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1991", - "2": "69.90000152587889", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1992", - "2": "71.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1993", - "2": "69.0", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1994", - "2": "68.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1995", - "2": "67.0", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1996", - "2": "65.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1997", - "2": "61.7999992370605", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1998", - "2": "62.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "1999", - "2": "59.7000007629395", - "3": "false", - "4": "true" - }, - { - "0": "23", - "1": "2000", - "2": "53.7999992370605", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1970", - "2": "172.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1971", - "2": "187.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1972", - "2": "214.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1973", - "2": "226.5", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1974", - "2": "227.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1975", - "2": "226.0", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1976", - "2": "230.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1977", - "2": "217.0", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1978", - "2": "205.5", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1979", - "2": "197.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1980", - "2": "187.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1981", - "2": "179.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1982", - "2": "179.0", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1983", - "2": "169.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1984", - "2": "160.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1985", - "2": "156.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1986", - "2": "154.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1987", - "2": "150.5", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1988", - "2": "146.0", - "3": "false", - "4": "false" - }, - { - "0": "24", - "1": "1989", - "2": "139.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1990", - "2": "133.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1991", - "2": "132.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1992", - "2": "128.899993896484", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1993", - "2": "129.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1994", - "2": "112.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1995", - "2": "124.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1996", - "2": "129.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1997", - "2": "125.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1998", - "2": "126.0", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "1999", - "2": "113.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "24", - "1": "2000", - "2": "109.0", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1970", - "2": "93.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1971", - "2": "98.5", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1972", - "2": "103.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1973", - "2": "108.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1974", - "2": "110.5", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1975", - "2": "117.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1976", - "2": "125.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1977", - "2": "122.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1978", - "2": "121.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1979", - "2": "121.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1980", - "2": "123.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1981", - "2": "125.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1982", - "2": "126.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1983", - "2": "119.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1984", - "2": "109.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1985", - "2": "103.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1986", - "2": "99.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1987", - "2": "92.3000030517578", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1988", - "2": "87.0999984741211", - "3": "false", - "4": "false" - }, - { - "0": "25", - "1": "1989", - "2": "84.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1990", - "2": "77.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1991", - "2": "85.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1992", - "2": "74.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1993", - "2": "83.0", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1994", - "2": "81.0", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1995", - "2": "80.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1996", - "2": "80.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1997", - "2": "77.5", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1998", - "2": "79.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "1999", - "2": "74.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "25", - "1": "2000", - "2": "72.5", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1970", - "2": "121.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1971", - "2": "124.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1972", - "2": "124.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1973", - "2": "120.5", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1974", - "2": "122.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1975", - "2": "122.5", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1976", - "2": "124.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1977", - "2": "127.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1978", - "2": "131.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1979", - "2": "130.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1980", - "2": "133.5", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1981", - "2": "132.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1982", - "2": "134.0", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1983", - "2": "130.0", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1984", - "2": "127.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1985", - "2": "126.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1986", - "2": "126.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1987", - "2": "124.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1988", - "2": "122.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "26", - "1": "1989", - "2": "118.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1990", - "2": "115.5", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1991", - "2": "113.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1992", - "2": "112.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1993", - "2": "108.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1994", - "2": "108.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1995", - "2": "111.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1996", - "2": "107.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1997", - "2": "108.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1998", - "2": "106.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "1999", - "2": "104.0", - "3": "false", - "4": "true" - }, - { - "0": "26", - "1": "2000", - "2": "99.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1970", - "2": "108.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1971", - "2": "115.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1972", - "2": "121.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1973", - "2": "124.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1974", - "2": "130.5", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1975", - "2": "132.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1976", - "2": "138.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1977", - "2": "140.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1978", - "2": "143.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1979", - "2": "141.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1980", - "2": "141.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1981", - "2": "143.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1982", - "2": "147.0", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1983", - "2": "140.0", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1984", - "2": "128.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1985", - "2": "124.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1986", - "2": "119.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1987", - "2": "113.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1988", - "2": "103.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "27", - "1": "1989", - "2": "97.5", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1990", - "2": "88.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1991", - "2": "87.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1992", - "2": "86.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1993", - "2": "86.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1994", - "2": "104.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1995", - "2": "109.5", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1996", - "2": "110.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1997", - "2": "111.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1998", - "2": "112.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "1999", - "2": "111.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "27", - "1": "2000", - "2": "108.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1970", - "2": "107.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1971", - "2": "106.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1972", - "2": "109.0", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1973", - "2": "110.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1974", - "2": "114.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1975", - "2": "114.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1976", - "2": "118.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1977", - "2": "120.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1978", - "2": "122.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1979", - "2": "122.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1980", - "2": "124.0", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1981", - "2": "125.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1982", - "2": "123.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1983", - "2": "125.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1984", - "2": "115.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1985", - "2": "115.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1986", - "2": "113.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1987", - "2": "110.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1988", - "2": "107.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "28", - "1": "1989", - "2": "107.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1990", - "2": "101.300003051758", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1991", - "2": "102.5", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1992", - "2": "96.1999969482422", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1993", - "2": "94.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1994", - "2": "95.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1995", - "2": "95.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1996", - "2": "93.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1997", - "2": "92.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1998", - "2": "92.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "1999", - "2": "91.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "28", - "1": "2000", - "2": "87.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1970", - "2": "123.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1971", - "2": "123.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1972", - "2": "134.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1973", - "2": "142.0", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1974", - "2": "146.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1975", - "2": "154.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1976", - "2": "150.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1977", - "2": "148.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1978", - "2": "146.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1979", - "2": "145.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1980", - "2": "149.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1981", - "2": "151.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1982", - "2": "146.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1983", - "2": "135.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1984", - "2": "136.899993896484", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1985", - "2": "133.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1986", - "2": "136.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1987", - "2": "124.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1988", - "2": "138.0", - "3": "false", - "4": "false" - }, - { - "0": "29", - "1": "1989", - "2": "120.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1990", - "2": "101.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1991", - "2": "103.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1992", - "2": "100.099998474121", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1993", - "2": "94.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1994", - "2": "91.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1995", - "2": "90.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1996", - "2": "87.5", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1997", - "2": "90.0", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1998", - "2": "88.6999969482422", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "1999", - "2": "86.9000015258789", - "3": "false", - "4": "true" - }, - { - "0": "29", - "1": "2000", - "2": "83.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1970", - "2": "103.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1971", - "2": "115.0", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1972", - "2": "118.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1973", - "2": "125.5", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1974", - "2": "129.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1975", - "2": "130.5", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1976", - "2": "136.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1977", - "2": "137.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1978", - "2": "140.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1979", - "2": "135.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1980", - "2": "138.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1981", - "2": "136.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1982", - "2": "136.0", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1983", - "2": "131.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1984", - "2": "127.0", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1985", - "2": "125.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1986", - "2": "126.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1987", - "2": "126.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1988", - "2": "124.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "30", - "1": "1989", - "2": "122.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1990", - "2": "118.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1991", - "2": "121.5", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1992", - "2": "112.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1993", - "2": "115.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1994", - "2": "112.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1995", - "2": "109.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1996", - "2": "102.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1997", - "2": "124.5", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1998", - "2": "126.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "1999", - "2": "109.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "30", - "1": "2000", - "2": "103.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1970", - "2": "92.6999969482422", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1971", - "2": "96.6999969482422", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1972", - "2": "103.0", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1973", - "2": "103.5", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1974", - "2": "108.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1975", - "2": "113.5", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1976", - "2": "116.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1977", - "2": "115.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1978", - "2": "116.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1979", - "2": "117.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1980", - "2": "114.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1981", - "2": "115.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1982", - "2": "113.0", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1983", - "2": "109.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1984", - "2": "105.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1985", - "2": "104.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1986", - "2": "97.0", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1987", - "2": "95.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1988", - "2": "91.9000015258789", - "3": "false", - "4": "false" - }, - { - "0": "31", - "1": "1989", - "2": "87.4000015258789", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1990", - "2": "88.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1991", - "2": "91.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1992", - "2": "93.0", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1993", - "2": "91.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1994", - "2": "94.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1995", - "2": "98.5999984741211", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1996", - "2": "92.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1997", - "2": "88.8000030517578", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1998", - "2": "88.3000030517578", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "1999", - "2": "83.5", - "3": "false", - "4": "true" - }, - { - "0": "31", - "1": "2000", - "2": "75.0999984741211", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1970", - "2": "99.8000030517578", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1971", - "2": "106.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1972", - "2": "111.5", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1973", - "2": "109.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1974", - "2": "114.800003051758", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1975", - "2": "117.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1976", - "2": "121.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1977", - "2": "124.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1978", - "2": "127.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1979", - "2": "127.199996948242", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1980", - "2": "130.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1981", - "2": "129.100006103516", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1982", - "2": "131.399993896484", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1983", - "2": "129.0", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1984", - "2": "125.099998474121", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1985", - "2": "128.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1986", - "2": "129.0", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1987", - "2": "130.600006103516", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1988", - "2": "125.300003051758", - "3": "false", - "4": "false" - }, - { - "0": "32", - "1": "1989", - "2": "124.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1990", - "2": "121.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1991", - "2": "120.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1992", - "2": "121.0", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1993", - "2": "120.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1994", - "2": "118.800003051758", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1995", - "2": "125.400001525879", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1996", - "2": "119.199996948242", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1997", - "2": "118.900001525879", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1998", - "2": "119.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "1999", - "2": "115.599998474121", - "3": "false", - "4": "true" - }, - { - "0": "32", - "1": "2000", - "2": "108.699996948242", - "3": "false", - "4": "true" - }, - { - "0": "33", - "1": "1970", - "2": "106.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1971", - "2": "108.900001525879", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1972", - "2": "108.599998474121", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1973", - "2": "110.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1974", - "2": "114.699996948242", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1975", - "2": "116.0", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1976", - "2": "121.400001525879", - "3": "false", - "4": "false" - }, - { - "0": "33", - "1": "1977", - "2": "124.199996948242", - "3": "false", - "4": "false" - } - ], - "schema": [ - { - "key": "0", - "name": "state", - "type": "int" - }, - { - "key": "1", - "name": "year", - "type": "int" - }, - { - "key": "2", - "name": "cigsale", - "type": "double" - }, - { - "key": "3", - "name": "california", - "type": "boolean" - }, - { - "key": "4", - "name": "after_treatment", - "type": "boolean" - } - ], - "truncated": false - } - }, - "type": "Synapse.DataFrame" - } - }, + "state": {}, "version": "0.1" } }, From f1bf70193d9919b072f7d0d1affa3124a69a34f5 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 3 Jan 2024 18:22:51 -0800 Subject: [PATCH 41/41] remove invalid image links --- .../Quickstart - Measure Causal Effects.ipynb | 15 +-------------- ...ickstart - Measure Heterogeneous Effects.ipynb | 13 ------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Causal Effects.ipynb b/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Causal Effects.ipynb index e584a20e37..d459db5a5b 100644 --- a/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Causal Effects.ipynb +++ b/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Causal Effects.ipynb @@ -17,19 +17,6 @@ "# Startup Investment Attribution - Understand Outreach Effort's Effect\"" ] }, - { - "cell_type": "markdown", - "metadata": { - "nteract": { - "transient": { - "deleting": false - } - } - }, - "source": [ - "![image-alt-text](https://camo.githubusercontent.com/4ac8c931fd4600d2b466975c87fb03b439ebc7f6debd58409aea0db10457436d/68747470733a2f2f7777772e6d6963726f736f66742e636f6d2f656e2d75732f72657365617263682f75706c6f6164732f70726f642f323032302f30352f4174747269627574696f6e2e706e67)" - ] - }, { "attachments": {}, "cell_type": "markdown", @@ -238,4 +225,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Heterogeneous Effects.ipynb b/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Heterogeneous Effects.ipynb index d814880289..3792fe4c3f 100644 --- a/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Heterogeneous Effects.ipynb +++ b/docs/Explore Algorithms/Causal Inference/Quickstart - Measure Heterogeneous Effects.ipynb @@ -17,19 +17,6 @@ "# Startup Investment Attribution - Understand Outreach Effort's Effect\"" ] }, - { - "cell_type": "markdown", - "metadata": { - "nteract": { - "transient": { - "deleting": false - } - } - }, - "source": [ - "![image-alt-text](https://camo.githubusercontent.com/4ac8c931fd4600d2b466975c87fb03b439ebc7f6debd58409aea0db10457436d/68747470733a2f2f7777772e6d6963726f736f66742e636f6d2f656e2d75732f72657365617263682f75706c6f6164732f70726f642f323032302f30352f4174747269627574696f6e2e706e67)" - ] - }, { "cell_type": "markdown", "metadata": {},