一、工具类
import com.siwei.energy.optimization.pojo.dto.NumberDTO;
/**
* @author 咸鱼搬运工
*/
public class LinearRegressionUtil {
private static final int MAX_POINTS = 10;
private double E;
/**
* Print the computed sums.
* 获取数据点数
*
* @param line the regression line
*/
private static void printSums(RegressionLine line) {
System.out.println("\n数据点个数 n = " + line.getDataPointCount());
System.out.println("\nSum x = " + line.getSumX());
System.out.println("Sum y = " + line.getSumY());
System.out.println("Sum xx = " + line.getSumXX());
System.out.println("Sum xy = " + line.getSumXY());
System.out.println("Sum yy = " + line.getSumYY());
}
/**
* Print the regression line function.
*
* @param line the regression line
*/
public static NumberDTO printLine(RegressionLine line) {
//回归线公式
String formula = "y = " + line.getA1() + "x + " + line.getA0();
//误差
double mistake = line.getR();
System.out.println(formula);
System.out.println(mistake);
return new NumberDTO().setNumA(line.getA1()).setNumB(line.getA0());
}
}
二、实现
import cn.hutool.core.collection.CollUtil;
import com.siwei.energy.constant.EnergyConstant;
import com.siwei.energy.optimization.pojo.dto.DateDTO;
import com.siwei.energy.optimization.pojo.dto.NumberDTO;
import com.siwei.energy.optimization.pojo.dto.QueryDTO;
import com.siwei.energy.optimization.pojo.po.DataPoint;
import com.siwei.energy.optimization.pojo.vo.EnergyPredictVO;
import com.siwei.energy.optimization.service.EnergyOptimizationService;
import com.siwei.energy.utils.LinearRegressionUtil;
import com.siwei.energy.utils.RegressionLine;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* 能源优化
*
* @Author: yangJing
* @Date: 2022-06-15 09:53
*/
@Service
public class EnergyOptimizationServiceImpl implements EnergyOptimizationService {
@Override
@SneakyThrows
public List<EnergyPredictVO> energyPredict(QueryDTO query) {
//获取时间区间
DateDTO futureDate = getOldDate(query.getDateType());
// 获取公式系数 y = -5.6x + 146.0
// 模拟假数据
// 获取所有数据
List<EnergyPredictVO> list = constructFakeData();
if (CollUtil.isNotEmpty(list)) {
RegressionLine energyPredicts = calculateLinearFormula(list);
NumberDTO numberDTO = LinearRegressionUtil.printLine(energyPredicts);
// 处理已经存在的数据
Integer num = 0;
for (EnergyPredictVO e : list) {
num++;
float predict = numberDTO.getNumA() * num + numberDTO.getNumB();
e.setPredictPower((float) (Math.round(predict * 100)) / 100);
}
// 天数
Integer count = futureDate.getCount();
// 预测未来的数据
List<EnergyPredictVO> collect = list.stream().sorted(Comparator.comparing(EnergyPredictVO::getDate).reversed()).collect(Collectors.toList());
ArrayList<EnergyPredictVO> temps = new ArrayList<>();
// 获取时间最大的对象
if (CollUtil.isNotEmpty(collect)) {
EnergyPredictVO energyPredictVO = collect.stream().findFirst().orElse(null);
String date = energyPredictVO.getDate();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date f = simpleDateFormat.parse(date);
// 时间天数
int temp = 0;
for (int i = list.size() + 1; i <= count; i++) {
temp++;
Date tempDate = new Date(f.getTime() + 1000L * 60 * 60 * 24 * temp);
// 预测值
float predict = numberDTO.getNumA() * i + numberDTO.getNumB();
temps.add(new EnergyPredictVO().setDate(simpleDateFormat.format(tempDate)).setPredictPower((float) (Math.round(predict * 100)) / 100));
}
}
list.addAll(temps);
}
return list;
}
/**
* 计算线性公式
*
* @param
* @return
*/
private RegressionLine calculateLinearFormula(List<EnergyPredictVO> list) {
float count = 0;
RegressionLine line = new RegressionLine();
for (EnergyPredictVO e : list) {
count++;
line.addDataPoint(new DataPoint(count, Float.parseFloat(e.getActualPower().toString())));
}
return line;
}
/**
* 假数据
*
* @param
* @return
*/
private List<EnergyPredictVO> constructFakeData() {
ArrayList<EnergyPredictVO> list = new ArrayList<>();
EnergyPredictVO e = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(10.215)).setTargetPower(BigDecimal.valueOf(10)).setDate("2022-06-10");
EnergyPredictVO e1 = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(20.326)).setTargetPower(BigDecimal.valueOf(120)).setDate("2022-06-11");
EnergyPredictVO e2 = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(30.15)).setTargetPower(BigDecimal.valueOf(143)).setDate("2022-06-12");
EnergyPredictVO e3 = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(40.13)).setTargetPower(BigDecimal.valueOf(130)).setDate("2022-06-13");
EnergyPredictVO e4 = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(30.524)).setTargetPower(BigDecimal.valueOf(125)).setDate("2022-06-14");
EnergyPredictVO e5 = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(60.26)).setTargetPower(BigDecimal.valueOf(126)).setDate("2022-06-15");
EnergyPredictVO e6 = new EnergyPredictVO().setActualPower(BigDecimal.valueOf(70.441)).setTargetPower(BigDecimal.valueOf(122)).setDate("2022-06-16");
list.add(e);
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
list.add(e5);
list.add(e6);
return list;
}
/**
* 获取起始时间时间
*
* @param type 年月日类型
* @return
*/
private DateDTO getOldDate(String type) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar calendar = Calendar.getInstance();
String endTime;
String startTime;
Integer count;
if (EnergyConstant.Time.DAY.equals(type)) {
// 前后六天
calendar.add(Calendar.DATE, -6);
startTime = sdf.format(calendar.getTime());
calendar.add(Calendar.DATE, 12);
endTime = sdf.format(calendar.getTime());
count = EnergyConstant.Time.DAY_COUNT;
} else if (EnergyConstant.Time.MONTH.equals(type)) {
//前后三个月
calendar.add(Calendar.DATE, -3);
startTime = sdf.format(calendar.getTime());
calendar.add(Calendar.DATE, 6);
endTime = sdf.format(calendar.getTime());
count = EnergyConstant.Time.MONTH_COUNT;
} else {
// 前后两年
calendar.add(Calendar.DATE, -2);
startTime = sdf.format(calendar.getTime());
calendar.add(Calendar.DATE, 4);
endTime = sdf.format(calendar.getTime());
count = EnergyConstant.Time.YEAR_COUNT;
}
return new DateDTO().setStartTime(startTime).setEndTime(endTime).setCount(count);
}
}
三、业务参数
@Data
@Accessors(chain = true)
public class DateDTO {
private String startTime;
private String endTime;
private Integer count;
}
@Data
@ApiModel("时间类型dto")
public class QueryDTO {
/**
* 日月年
*/
@NotBlank(message = "年月日类型不能为空!")
@ApiModelProperty(value = "年月日类型:day,mouth,year")
private String dateType;
}
@Data
@Accessors(chain = true)
public class EnergyPredictVO {
/**
* 日期
*/
private String date;
/**
* 实际电耗
*/
private BigDecimal actualPower;
/**
* 目标电耗
*/
private BigDecimal targetPower;
/**
* 预测电耗
*/
private Float predictPower;
}
public interface EnergyConstant {
/**
* 时间
*/
interface Time{
//24小小时转化分钟
Integer ALL_DAY = 1440;
Integer DAY_COUNT = 12;
Integer MONTH_COUNT = 6;
Integer YEAR_COUNT = 4;
//日
String DAY = "day";
//月
String MONTH = "month";
//年
String YEAR = "year";
}
}
四、公式参数
@Data
@Accessors(chain = true)
public class NumberDTO {
/**
* 系数一
*/
private Float numA;
/**
* 系数二
*/
private Float numB;
}
public class DataPoint {
/** the x value */
public float x;
/** the y value */
public float y;
/**
* Constructor.
*
* @param x
* the x value
* @param y
* the y value
*/
public DataPoint(float x, float y) {
this.x = x;
this.y = y;
}
}
public class RegressionLine {
/**
* sum of x
*/
private double sumX;
/**
* sum of y
*/
private double sumY;
/**
* sum of x*x
*/
private double sumXX;
/**
* sum of x*y
*/
private double sumXY;
/**
* sum of y*y
*/
private double sumYY;
/**
* sum of yi-y
*/
private double sumDeltaY;
/**
* sum of sumDeltaY^2
*/
private double sumDeltaY2;
/**
* 误差
*/
private double sse;
private double sst;
private double E;
private String[] xy;
private ArrayList listX;
private ArrayList listY;
private int XMin, XMax, YMin, YMax;
/**
* line coefficient a0
*/
private float a0;
/**
* line coefficient a1
*/
private float a1;
/**
* number of data points
*/
private int pn;
/**
* true if coefficients valid
*/
private boolean coefsValid;
/**
* Constructor.
*/
public RegressionLine() {
XMax = 0;
YMax = 0;
pn = 0;
xy = new String[2];
listX = new ArrayList();
listY = new ArrayList();
}
/**
* Constructor.
*
* @param data the array of data points
*/
public RegressionLine(DataPoint data[]) {
pn = 0;
xy = new String[2];
listX = new ArrayList();
listY = new ArrayList();
for (int i = 0; i < data.length; ++i) {
addDataPoint(data[i]);
}
}
/**
* Return the current number of data points.
*
* @return the count
*/
public int getDataPointCount() {
return pn;
}
/**
* Return the coefficient a0.
*
* @return the value of a0
*/
public float getA0() {
validateCoefficients();
return a0;
}
/**
* Return the coefficient a1.
*
* @return the value of a1
*/
public float getA1() {
validateCoefficients();
return a1;
}
/**
* Return the sum of the x values.
*
* @return the sum
*/
public double getSumX() {
return sumX;
}
/**
* Return the sum of the y values.
*
* @return the sum
*/
public double getSumY() {
return sumY;
}
/**
* Return the sum of the x*x values.
*
* @return the sum
*/
public double getSumXX() {
return sumXX;
}
/**
* Return the sum of the x*y values.
*
* @return the sum
*/
public double getSumXY() {
return sumXY;
}
public double getSumYY() {
return sumYY;
}
public int getXMin() {
return XMin;
}
public int getXMax() {
return XMax;
}
public int getYMin() {
return YMin;
}
public int getYMax() {
return YMax;
}
/**
* Add a new data point: Update the sums.
*
* @param dataPoint the new data point
*/
public void addDataPoint(DataPoint dataPoint) {
sumX += dataPoint.x;
sumY += dataPoint.y;
sumXX += dataPoint.x * dataPoint.x;
sumXY += dataPoint.x * dataPoint.y;
sumYY += dataPoint.y * dataPoint.y;
if (dataPoint.x > XMax) {
XMax = (int) dataPoint.x;
}
if (dataPoint.y > YMax) {
YMax = (int) dataPoint.y;
}
// 把每个点的具体坐标存入ArrayList中,备用
xy[0] = (int) dataPoint.x + "";
xy[1] = (int) dataPoint.y + "";
if (dataPoint.x != 0 && dataPoint.y != 0) {
System.out.print(xy[0] + ",");
System.out.println(xy[1]);
try {
// System.out.println("n:"+n);
listX.add(pn, xy[0]);
listY.add(pn, xy[1]);
} catch (Exception e) {
e.printStackTrace();
}
/*
* System.out.println("N:" + n); System.out.println("ArrayList
* listX:"+ listX.get(n)); System.out.println("ArrayList listY:"+
* listY.get(n));
*/
}
++pn;
coefsValid = false;
}
/**
* Return the value of the regression line function at x. (Implementation of
* Evaluatable.)
*
* @param x the value of x
* @return the value of the function at x
*/
public float at(int x) {
if (pn < 2)
return Float.NaN;
validateCoefficients();
return a0 + a1 * x;
}
/**
* Reset.
*/
public void reset() {
pn = 0;
sumX = sumY = sumXX = sumXY = 0;
coefsValid = false;
}
/**
* Validate the coefficients. 计算方程系数 y=ax+b 中的a
*/
private void validateCoefficients() {
if (coefsValid) {
return;
}
if (pn >= 2) {
float xBar = (float) sumX / pn;
float yBar = (float) sumY / pn;
a1 = (float) ((pn * sumXY - sumX * sumY) / (pn * sumXX - sumX
* sumX));
a0 = (float) (yBar - a1 * xBar);
} else {
a0 = a1 = Float.NaN;
}
coefsValid = true;
}
/**
* 返回误差
*/
public double getR() {
// 遍历这个list并计算分母
for (int i = 0; i < pn - 1; i++) {
float Yi = (float) Integer.parseInt(listY.get(i).toString());
float Y = at(Integer.parseInt(listX.get(i).toString()));
float deltaY = Yi - Y;
float deltaY2 = deltaY * deltaY;
/*
* System.out.println("Yi:" + Yi); System.out.println("Y:" + Y);
* System.out.println("deltaY:" + deltaY);
* System.out.println("deltaY2:" + deltaY2);
*/
sumDeltaY2 += deltaY2;
// System.out.println("sumDeltaY2:" + sumDeltaY2);
}
sst = sumYY - (sumY * sumY) / pn;
// System.out.println("sst:" + sst);
E = 1 - sumDeltaY2 / sst;
return round(E, 4);
}
// 用于实现精确的四舍五入
public double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
public float round(float v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).floatValue();
}
}