diff --git a/module-common/src/main/java/com/gunshi/project/hsz/common/mapper/JcskSyRMapper.java b/module-common/src/main/java/com/gunshi/project/hsz/common/mapper/JcskSyRMapper.java index eb58423..fc8fa30 100644 --- a/module-common/src/main/java/com/gunshi/project/hsz/common/mapper/JcskSyRMapper.java +++ b/module-common/src/main/java/com/gunshi/project/hsz/common/mapper/JcskSyRMapper.java @@ -11,6 +11,7 @@ import com.gunshi.project.hsz.common.model.so.JcskSyRPageSo; import com.gunshi.project.hsz.common.model.so.OsmoticDetailQuerySo; import com.gunshi.project.hsz.common.model.so.OsmoticDetailQuerySo; +import jakarta.validation.constraints.Size; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; @@ -362,4 +363,25 @@ GROUP BY dm """) Page artificialPage(Page page,@Param("dto") JcskSyRPageSo page1); + + @Select(""" + WITH ranked_data AS ( + SELECT + tm, + rz, + ABS(EXTRACT(HOUR FROM tm) + EXTRACT(MINUTE FROM tm)/60.0 - 8) as time_diff, + ROW_NUMBER() OVER (PARTITION BY DATE(tm) ORDER BY ABS(EXTRACT(HOUR FROM tm) + EXTRACT(MINUTE FROM tm)/60.0 - 8)) as rn + FROM st_rsvr_r + WHERE stcd = #{stcd} + AND tm >= #{dto.dateTimeRangeSo.start} + AND tm <= #{dto.dateTimeRangeSo.end} + ) + SELECT + TO_CHAR(DATE(tm) + INTERVAL '8 hours', 'YYYY-MM-DD HH24:MI:SS') as tm, + rz + FROM ranked_data + WHERE rn = 1 + ORDER BY tm +""") + List qeury8AmRz(@Param("dto") OsmoticQuerySo dto, @Param("stcd") String stcd); } diff --git a/module-common/src/main/java/com/gunshi/project/hsz/common/model/vo/OsmoticPressDetailVo.java b/module-common/src/main/java/com/gunshi/project/hsz/common/model/vo/OsmoticPressDetailVo.java index f148578..fd41e31 100644 --- a/module-common/src/main/java/com/gunshi/project/hsz/common/model/vo/OsmoticPressDetailVo.java +++ b/module-common/src/main/java/com/gunshi/project/hsz/common/model/vo/OsmoticPressDetailVo.java @@ -12,17 +12,17 @@ import java.math.BigDecimal; * @author wanyan * @version 1.0 */ -@Data -public class OsmoticPressDetailVo { + @Data + public class OsmoticPressDetailVo { - @Schema(description="监测时间") - private String tm; + @Schema(description="监测时间") + private String tm; - @Schema(description="监测值") - private BigDecimal value; + @Schema(description="监测值") + private BigDecimal value; - @Schema(description="库水位") - private BigDecimal rz; + @Schema(description="库水位") + private BigDecimal rz; -} + } diff --git a/src/main/java/com/gunshi/project/hsz/controller/ProjectSafeAnalyseController.java b/src/main/java/com/gunshi/project/hsz/controller/ProjectSafeAnalyseController.java new file mode 100644 index 0000000..b02ad0c --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/controller/ProjectSafeAnalyseController.java @@ -0,0 +1,90 @@ +package com.gunshi.project.hsz.controller; + +import com.gunshi.core.result.R; +import com.gunshi.project.hsz.common.model.vo.OsmoticPressDetailVo; +import com.gunshi.project.hsz.entity.dto.ProjectSafeCalculateDto; +import com.gunshi.project.hsz.entity.dto.ProjectSaveReportDto; +import com.gunshi.project.hsz.entity.dto.SyRegressionDataDto; +import com.gunshi.project.hsz.entity.vo.ForecastResultVo; +import com.gunshi.project.hsz.model.ForecastTask; +import com.gunshi.project.hsz.model.RegressionEquation; +import com.gunshi.project.hsz.model.SyRegressionData; +import com.gunshi.project.hsz.service.ForecastResultsService; +import com.gunshi.project.hsz.service.JcskSyRService; +import com.gunshi.project.hsz.service.SyRegressionDataService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +@Tag(name = "工程安全监测") +@RestController +@RequestMapping(value="/projectSafe") +public class ProjectSafeAnalyseController { + + + @Autowired + private JcskSyRService jcskSyRService; + + @Autowired + private SyRegressionDataService syRegressionDataService; + + @Autowired + private ForecastResultsService forecastResultsService; + + @Operation(summary = "多项式回归") + @PostMapping("/caculate") + public R> calculate(@RequestBody ProjectSafeCalculateDto dto){ + Map ans = jcskSyRService.calculate(dto); + return R.ok(ans); + } + + @Operation(summary = "预报") + @PostMapping("/report") + public R> report(@RequestBody ProjectSaveReportDto projectSaveReportDto){ + ForecastTask forecastTask = projectSaveReportDto.getForecastTask(); + List voList = forecastResultsService.getHumanForecastResult(forecastTask); + SyRegressionData syRegressionData = new SyRegressionData(); + syRegressionData.setDvcd(projectSaveReportDto.getDvcd()); + syRegressionData.setOrder(projectSaveReportDto.getOrder()); + List queryReg = syRegressionDataService.queryData(syRegressionData);//获取方程组 + if(queryReg.isEmpty()){ + return R.ok(null); + } + //计算 + List res = forecastResultsService.calculateY(queryReg.get(0),voList); + return R.ok(res); + } + + @Operation(summary = "保存多线程回归方程") + @PostMapping("/save") + public R save(@RequestBody SyRegressionDataDto dto){ + if(!dto.getList().isEmpty()){ + syRegressionDataService.saveBatch(dto.getList()); + } + return R.ok(true); + } + + @Operation(summary = "删除多线程回归方程") + @PostMapping("/del") + public R delete(@RequestBody SyRegressionDataDto dto){ + if(!dto.getList().isEmpty()){ + syRegressionDataService.removeBatchByIds(dto.getList()); + } + return R.ok(true); + } + + @Operation(summary = "查找多线程回归方程") + public R> list(@RequestBody SyRegressionData dto){ + List res = syRegressionDataService.queryData(dto); + return R.ok(res); + } + + +} diff --git a/src/main/java/com/gunshi/project/hsz/controller/SyDataCheckRuleController.java b/src/main/java/com/gunshi/project/hsz/controller/SyDataCheckRuleController.java new file mode 100644 index 0000000..f78ef4f --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/controller/SyDataCheckRuleController.java @@ -0,0 +1,64 @@ +package com.gunshi.project.hsz.controller; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.gunshi.core.result.R; +import com.gunshi.project.hsz.common.model.JcskGnssR; +import com.gunshi.project.hsz.common.model.JcskSyB; +import com.gunshi.project.hsz.common.model.so.JcskGnssRPageSo; +import com.gunshi.project.hsz.common.validate.markers.Insert; +import com.gunshi.project.hsz.common.validate.markers.Update; +import com.gunshi.project.hsz.entity.so.SyDataCheckRulePageSo; +import com.gunshi.project.hsz.model.SyDataCheckRule; +import com.gunshi.project.hsz.service.SyDataCheckRuleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Objects; + +@Tag(name = "渗压数据清洗规则") +@RestController +@RequestMapping(value="/syDataCheckRule") +public class SyDataCheckRuleController { + + @Autowired + private SyDataCheckRuleService syDataCheckRuleService; + + + @Operation(summary = "分页") + @PostMapping("/page") + public R> page(@RequestBody @Validated SyDataCheckRulePageSo page) { + return R.ok(syDataCheckRuleService.pageQuery(page)); + } + + + + @Operation(summary = "新增") + @PostMapping("/insert") + public R insert(@Validated(Insert.class) @RequestBody SyDataCheckRule dto) { + return R.ok(syDataCheckRuleService.saveData(dto)); + } + + @Operation(summary = "修改") + @PostMapping("/update") + public R update(@Validated(Update.class) @RequestBody SyDataCheckRule dto) { + return R.ok(syDataCheckRuleService.updateData(dto)); + } + + @Operation(summary = "删除") + @GetMapping("/del/{id}") + public R del(@Schema(name = "id") @PathVariable("id") Serializable id) { + if (Objects.isNull(syDataCheckRuleService.getById(id))) { + throw new IllegalArgumentException("当前数据不存在"); + } + return R.ok(syDataCheckRuleService.removeById(id)); + } + + +} diff --git a/src/main/java/com/gunshi/project/hsz/entity/dto/ProjectSafeCalculateDto.java b/src/main/java/com/gunshi/project/hsz/entity/dto/ProjectSafeCalculateDto.java new file mode 100644 index 0000000..9faddc9 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/entity/dto/ProjectSafeCalculateDto.java @@ -0,0 +1,13 @@ +package com.gunshi.project.hsz.entity.dto; + +import com.gunshi.db.dto.DateTimeRangeSo; +import lombok.Data; + +@Data +public class ProjectSafeCalculateDto { + + + private DateTimeRangeSo dateTimeRangeSo;//时间范围 + + private String dvcd;//测点编号 +} diff --git a/src/main/java/com/gunshi/project/hsz/entity/dto/ProjectSaveReportDto.java b/src/main/java/com/gunshi/project/hsz/entity/dto/ProjectSaveReportDto.java new file mode 100644 index 0000000..f13e00d --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/entity/dto/ProjectSaveReportDto.java @@ -0,0 +1,15 @@ +package com.gunshi.project.hsz.entity.dto; + + +import com.gunshi.project.hsz.model.ForecastTask; +import lombok.Data; + +@Data +public class ProjectSaveReportDto { + + private ForecastTask forecastTask; + + private String dvcd; + + private Integer order; +} diff --git a/src/main/java/com/gunshi/project/hsz/entity/dto/SyRegressionDataDto.java b/src/main/java/com/gunshi/project/hsz/entity/dto/SyRegressionDataDto.java new file mode 100644 index 0000000..50171e5 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/entity/dto/SyRegressionDataDto.java @@ -0,0 +1,13 @@ +package com.gunshi.project.hsz.entity.dto; + +import com.gunshi.project.hsz.model.SyRegressionData; +import lombok.Data; + +import java.util.List; + +@Data +public class SyRegressionDataDto { + + + List list; +} diff --git a/src/main/java/com/gunshi/project/hsz/entity/so/SyDataCheckRulePageSo.java b/src/main/java/com/gunshi/project/hsz/entity/so/SyDataCheckRulePageSo.java new file mode 100644 index 0000000..2cfa16e --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/entity/so/SyDataCheckRulePageSo.java @@ -0,0 +1,18 @@ +package com.gunshi.project.hsz.entity.so; + + +import com.gunshi.db.dto.PageSo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class SyDataCheckRulePageSo { + + + @NotNull(message = "分页参数不能为空") + @Schema(description = "分页参数") + private PageSo pageSo; + + private String dvcd; +} diff --git a/src/main/java/com/gunshi/project/hsz/mapper/SyDataCheckRuleMapper.java b/src/main/java/com/gunshi/project/hsz/mapper/SyDataCheckRuleMapper.java new file mode 100644 index 0000000..62b0be1 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/mapper/SyDataCheckRuleMapper.java @@ -0,0 +1,9 @@ +package com.gunshi.project.hsz.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gunshi.project.hsz.model.SyDataCheckRule; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SyDataCheckRuleMapper extends BaseMapper { +} diff --git a/src/main/java/com/gunshi/project/hsz/mapper/SyRegressionDataMapper.java b/src/main/java/com/gunshi/project/hsz/mapper/SyRegressionDataMapper.java new file mode 100644 index 0000000..582ede1 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/mapper/SyRegressionDataMapper.java @@ -0,0 +1,11 @@ +package com.gunshi.project.hsz.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gunshi.project.hsz.model.SyRegressionData; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SyRegressionDataMapper extends BaseMapper { + +} diff --git a/src/main/java/com/gunshi/project/hsz/model/RegressionEquation.java b/src/main/java/com/gunshi/project/hsz/model/RegressionEquation.java new file mode 100644 index 0000000..c9de141 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/model/RegressionEquation.java @@ -0,0 +1,106 @@ +package com.gunshi.project.hsz.model; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 线性回归方程实体类 + */ +@Data +public class RegressionEquation { + /** + * 方程阶数 + */ + private int order; + + /** + * 方程系数(从常数项到最高次项) + * 例如:对于二次方程 y = ax² + bx + c,coefficients = [c, b, a] + */ + private List coefficients; + + /** + * 相关系数 R² + */ + private BigDecimal rSquared; + + /** + * 数据点数 + */ + private int dataCount; + + /** + * 方程字符串表示 + */ + private String equationString; + + public RegressionEquation(int order, List coefficients, BigDecimal rSquared, int dataCount) { + this.order = order; + this.coefficients = coefficients; + this.rSquared = rSquared; + this.dataCount = dataCount; + this.equationString = generateEquationString(); + } + + /** + * 生成方程字符串 + */ + private String generateEquationString() { + StringBuilder sb = new StringBuilder("y = "); + + for (int i = coefficients.size() - 1; i >= 0; i--) { + BigDecimal coeff = coefficients.get(i); + if (coeff.compareTo(BigDecimal.ZERO) == 0) { + continue; + } + + // 符号处理 + if (sb.length() > 4 && coeff.compareTo(BigDecimal.ZERO) > 0) { + sb.append(" + "); + } else if (sb.length() > 4 && coeff.compareTo(BigDecimal.ZERO) < 0) { + sb.append(" - "); + coeff = coeff.abs(); + } + + // 系数和变量 + if (i == 0) { + // 常数项 + sb.append(String.format("%.4f", coeff)); + } else { + // 非常数项 + if (coeff.compareTo(BigDecimal.ONE) != 0 && coeff.compareTo(BigDecimal.ONE.negate()) != 0) { + sb.append(String.format("%.4f", coeff)); + } + sb.append("x"); + if (i > 1) { + sb.append("^").append(i); + } + } + } + + return sb.toString(); + } + + /** + * 根据x值预测y值 + */ + public BigDecimal predict(BigDecimal x) { + BigDecimal result = BigDecimal.ZERO; + BigDecimal xPower = BigDecimal.ONE; + + for (int i = 0; i < coefficients.size(); i++) { + result = result.add(coefficients.get(i).multiply(xPower)); + xPower = xPower.multiply(x); + } + + return result; + } + + + @Override + public String toString() { + return String.format("%s", equationString); + } +} diff --git a/src/main/java/com/gunshi/project/hsz/model/SyDataCheckRule.java b/src/main/java/com/gunshi/project/hsz/model/SyDataCheckRule.java new file mode 100644 index 0000000..febdb21 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/model/SyDataCheckRule.java @@ -0,0 +1,70 @@ +package com.gunshi.project.hsz.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.gunshi.project.hsz.common.validate.markers.Insert; +import com.gunshi.project.hsz.common.validate.markers.Update; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** +* 描述: 数据检查规则表 +* author: xusan +* date: 2024-12-19 10:00:00 +*/ +@Schema(description = "数据检查规则表") +@Data +@TableName("public.sy_data_check_rule") +public class SyDataCheckRule implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + @Schema(description = "id") + @NotNull(message = "id不能为空", groups = {Update.class}) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** + * 测点编号 + */ + @TableField(value = "dvcd") + @Schema(description = "测点编号") + @NotBlank(message = "测点编号不能为空", groups = {Insert.class, Update.class}) + @Size(max = 255, message = "测点编号最大长度要小于 255") + private String dvcd; + + /** + * 库水位大于多少 + */ + @TableField(value = "rz") + @Schema(description = "库水位大于多少") + private BigDecimal rz; + + /** + * 渗压水位小于多少 + */ + @TableField(value = "sy_value") + @Schema(description = "渗压水位小于多少") + private BigDecimal syValue; + + /** + * 是否启用 0:否 1:是 + */ + @TableField(value = "is_available") + @Schema(description = "是否启用 0:否 1:是") + private Integer isAvailable; +} \ No newline at end of file diff --git a/src/main/java/com/gunshi/project/hsz/model/SyRegressionData.java b/src/main/java/com/gunshi/project/hsz/model/SyRegressionData.java new file mode 100644 index 0000000..1b2947d --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/model/SyRegressionData.java @@ -0,0 +1,91 @@ +package com.gunshi.project.hsz.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.gunshi.core.dateformat.DateFormatString; +import com.gunshi.project.hsz.common.validate.markers.Insert; +import com.gunshi.project.hsz.common.validate.markers.Update; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** +* 描述: 回归方程数据表 +* author: xusan +* date: 2024-12-19 10:00:00 +*/ +@Schema(description = "回归方程数据表") +@Data +@TableName("public.sy_regression_data") +public class SyRegressionData implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + @Schema(description = "id") + @NotNull(message = "id不能为空", groups = {Update.class}) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** + * 测点编号 + */ + @TableField(value = "dvcd") + @Schema(description = "测点编号") + @NotBlank(message = "测点编号不能为空", groups = {Insert.class, Update.class}) + @Size(max = 50, message = "测点编号最大长度要小于 50") + private String dvcd; + + /** + * 开始时间 + */ + @TableField(value = "starttime") + @Schema(description = "开始时间") + @NotNull(message = "开始时间不能为空", groups = {Insert.class, Update.class}) + @JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8") + private Date startTime; + + /** + * 结束时间 + */ + @TableField(value = "endtime") + @Schema(description = "结束时间") + @NotNull(message = "结束时间不能为空", groups = {Insert.class, Update.class}) + @JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8") + private Date endTime; + + /** + * 回归方程 + */ + @TableField(value = "regressionequation") + @Schema(description = "回归方程") + private String regressionEquation; + + /** + * 排序(一阶-四阶) + */ + @TableField(value = "_order") + @Schema(description = "排序(一阶-四阶)") + private Integer order; + + /** + * 创建时间(如果表中有此字段可以添加) + */ + @TableField(value = "create_time") + @Schema(description = "创建时间") + @JsonFormat(pattern = DateFormatString.YYYY_MM_DD_HH_MM_SS, timezone = "GMT+8") + private Date createTime; +} \ No newline at end of file diff --git a/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java b/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java index fe097a5..9c7da1a 100644 --- a/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java +++ b/src/main/java/com/gunshi/project/hsz/service/ForecastResultsService.java @@ -11,6 +11,7 @@ import com.gunshi.model.vo.FloodAlgorithemVo; import com.gunshi.project.hsz.common.model.StPptnR; import com.gunshi.project.hsz.common.model.StRsvrR; import com.gunshi.project.hsz.common.model.StStbprpB; +import com.gunshi.project.hsz.common.model.vo.OsmoticPressDetailVo; import com.gunshi.project.hsz.entity.vo.ForeRainStatVo; import com.gunshi.project.hsz.entity.vo.ForeRainTimeVo; import com.gunshi.project.hsz.entity.vo.ForecastResultVo; @@ -24,6 +25,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.math.RoundingMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; @@ -1137,4 +1139,195 @@ public class ForecastResultsService extends ServiceImpl calculateY(SyRegressionData syRegressionData, List voList) { + //过滤出预测时间的数据 + List collect = voList.stream().filter(o -> { + return o.getIspreDrp().equals("1"); + }).collect(Collectors.toList()); + + //获取方程组 + String regressionEquation = syRegressionData.getRegressionEquation(); + List res = new ArrayList<>(); + + //进行计算 + for (ForecastResultVo forecastResultVo : collect) { + // 获取预测水位值作为x + BigDecimal x = forecastResultVo.getYcSwHValue(); + if (x == null) { + continue; // 如果x为空,跳过计算 + } + + // 根据回归方程计算y值 + BigDecimal y = calculateRegressionValue(regressionEquation, x); + + // 构建返回结果 + OsmoticPressDetailVo detailVo = new OsmoticPressDetailVo(); + detailVo.setTm(forecastResultVo.getTm()); // 监测时间 + detailVo.setValue(y); // 计算得到的监测值 + detailVo.setRz(x); // 库水位(使用预测水位值) + + res.add(detailVo); + } + + // 按时间升序排列 + res.sort((o1, o2) -> { + if (o1.getTm() == null && o2.getTm() == null) { + return 0; + } + if (o1.getTm() == null) { + return -1; + } + if (o2.getTm() == null) { + return 1; + } + return o1.getTm().compareTo(o2.getTm()); + }); + + return res; + } + + /** + * 根据回归方程计算y值 + * @param equation 回归方程字符串 + * @param x 自变量x的值 + * @return 计算得到的y值 + */ + private BigDecimal calculateRegressionValue(String equation, BigDecimal x) { + if (equation == null || equation.trim().isEmpty()) { + return BigDecimal.ZERO; + } + + // 清理方程字符串,移除空格和y= + String cleanEquation = equation.replace("y =", "").replace(" ", "").toLowerCase(); + + try { + BigDecimal result; + // 根据方程的形式判断阶数并计算 + if (cleanEquation.contains("x^4")) { + result = calculateFourthOrder(cleanEquation, x); + } else if (cleanEquation.contains("x^3")) { + result = calculateThirdOrder(cleanEquation, x); + } else if (cleanEquation.contains("x^2")) { + result = calculateSecondOrder(cleanEquation, x); + } else { + result = calculateFirstOrder(cleanEquation, x); + } + // 四舍五入保留两位小数 + return result.setScale(2, RoundingMode.HALF_UP); + } catch (Exception e) { + // 计算异常时返回0 + return BigDecimal.ZERO; + } + } + + + /** + * 计算一阶线性方程 y = ax + b + */ + private BigDecimal calculateFirstOrder(String equation, BigDecimal x) { + // 解析方程系数,格式如:0.0455x+94.2395 + String[] parts = equation.split("x"); + if (parts.length < 2) { + return BigDecimal.ZERO; + } + + BigDecimal a = parseCoefficient(parts[0]); + BigDecimal b = parseCoefficient(parts[1]); + + return a.multiply(x).add(b); + } + + /** + * 计算二阶方程 y = ax^2 + bx + c + */ + private BigDecimal calculateSecondOrder(String equation, BigDecimal x) { + // 解析方程系数,格式如:-68.4211x^2+16312.8684x-972224.1397 + String[] parts = equation.split("x"); + if (parts.length < 3) { + return BigDecimal.ZERO; + } + + BigDecimal a = parseCoefficient(parts[0]); + BigDecimal b = parseCoefficient(parts[1].replace("^2", "")); + BigDecimal c = parseCoefficient(parts[2]); + + BigDecimal x2 = x.multiply(x); // x^2 + return a.multiply(x2).add(b.multiply(x)).add(c); + } + + /** + * 计算三阶方程 y = ax^3 + bx^2 + cx + d + */ + private BigDecimal calculateThirdOrder(String equation, BigDecimal x) { + // 解析方程系数,格式如:-2291.6667x^3+819497.9167x^2-97683901.8750x+3881297151.1650 + String[] parts = equation.split("x"); + if (parts.length < 4) { + return BigDecimal.ZERO; + } + + BigDecimal a = parseCoefficient(parts[0]); + BigDecimal b = parseCoefficient(parts[1].replace("^3", "").replace("^2", "")); + BigDecimal c = parseCoefficient(parts[2].replace("^2", "")); + BigDecimal d = parseCoefficient(parts[3]); + + BigDecimal x2 = x.multiply(x); // x^2 + BigDecimal x3 = x2.multiply(x); // x^3 + + return a.multiply(x3).add(b.multiply(x2)).add(c.multiply(x)).add(d); + } + + /** + * 计算四阶方程 y = ax^4 + bx^3 + cx^2 + dx + e + */ + private BigDecimal calculateFourthOrder(String equation, BigDecimal x) { + // 解析方程系数,格式如:-5.9039x^4+523.5482x^3+316095.2736x^2-57676816.2672x+2688986002.6804 + String[] parts = equation.split("x"); + if (parts.length < 5) { + return BigDecimal.ZERO; + } + + BigDecimal a = parseCoefficient(parts[0]); + BigDecimal b = parseCoefficient(parts[1].replace("^4", "").replace("^3", "")); + BigDecimal c = parseCoefficient(parts[2].replace("^3", "").replace("^2", "")); + BigDecimal d = parseCoefficient(parts[3].replace("^2", "")); + BigDecimal e = parseCoefficient(parts[4]); + + BigDecimal x2 = x.multiply(x); // x^2 + BigDecimal x3 = x2.multiply(x); // x^3 + BigDecimal x4 = x3.multiply(x); // x^4 + + return a.multiply(x4).add(b.multiply(x3)).add(c.multiply(x2)).add(d.multiply(x)).add(e); + } + + /** + * 解析系数,处理正负号和数字格式 + */ + private BigDecimal parseCoefficient(String coeffStr) { + if (coeffStr == null || coeffStr.isEmpty()) { + return BigDecimal.ZERO; + } + + // 处理空字符串情况 + if (coeffStr.equals("+") || coeffStr.equals("-")) { + coeffStr += "1"; + } + + // 处理没有显式数字的情况,如:x^2+x+1 中的第一个x系数为1 + if (coeffStr.equals("")) { + return BigDecimal.ONE; + } + + try { + return new BigDecimal(coeffStr); + } catch (NumberFormatException e) { + // 如果解析失败,尝试处理特殊情况 + if (coeffStr.equals("+")) { + return BigDecimal.ONE; + } else if (coeffStr.equals("-")) { + return BigDecimal.ONE.negate(); + } + return BigDecimal.ZERO; + } + } } diff --git a/src/main/java/com/gunshi/project/hsz/service/JcskSyRService.java b/src/main/java/com/gunshi/project/hsz/service/JcskSyRService.java index 104b511..2c8f994 100644 --- a/src/main/java/com/gunshi/project/hsz/service/JcskSyRService.java +++ b/src/main/java/com/gunshi/project/hsz/service/JcskSyRService.java @@ -13,11 +13,15 @@ import com.gunshi.project.hsz.entity.dto.ArtificialJcskSyDeleteDto; import com.gunshi.project.hsz.common.model.so.JcskSyRPageSo; import com.gunshi.project.hsz.common.model.so.OsmoticDetailQuerySo; import com.gunshi.project.hsz.common.model.so.OsmoticQuerySo; +import com.gunshi.project.hsz.entity.dto.ProjectSafeCalculateDto; import com.gunshi.project.hsz.entity.vo.*; import com.gunshi.project.hsz.common.mapper.JcskSlBMapper; import com.gunshi.project.hsz.mapper.JcskSyREightAmMapper; import com.gunshi.project.hsz.common.mapper.JcskSyRMapper; import com.gunshi.project.hsz.common.model.JcskSyB; +import com.gunshi.project.hsz.model.AttResBase; +import com.gunshi.project.hsz.model.JcskSyREightAm; +import com.gunshi.project.hsz.model.RegressionEquation; import com.gunshi.project.hsz.util.*; import com.ruoyi.common.utils.StringUtils; import jakarta.servlet.http.HttpServletResponse; @@ -30,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.net.URLEncoder; import java.text.SimpleDateFormat; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -776,4 +781,98 @@ public class JcskSyRService extends ServiceImpl { int delete = this.baseMapper.delete(queryWrapper); return delete > 0; } + + @Autowired + private AttResBaseService attResBaseService; + + /** + * 多项式方程计算 + * @param dto + * @return + */ + public Map calculate(ProjectSafeCalculateDto dto) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + Map res = new HashMap<>(); + //根据dvcd查询stcd和mpcd + queryWrapper.eq(JcskSyB::getDvcd, dto.getDvcd()); + JcskSyB jcskSyB = jcskSyBService.getBaseMapper().selectOne(queryWrapper); + String stcd = jcskSyB.getStcd(); + String mpcd = jcskSyB.getMpcd(); + //取得这个时间段得所有八点得数据 + LambdaQueryWrapper eightAmLambdaQueryWrapper = new LambdaQueryWrapper<>(); + eightAmLambdaQueryWrapper.eq(JcskSyREightAm::getStcd, stcd); + eightAmLambdaQueryWrapper.eq(JcskSyREightAm::getMpcd, mpcd); + eightAmLambdaQueryWrapper.ge(JcskSyREightAm::getMstm,dto.getDateTimeRangeSo().getStart()); + eightAmLambdaQueryWrapper.le(JcskSyREightAm::getMstm,dto.getDateTimeRangeSo().getEnd()); + eightAmLambdaQueryWrapper.orderByAsc(JcskSyREightAm::getMstm); + List jcskSyREightAms = jcskSyREightAmMapper.selectList(eightAmLambdaQueryWrapper); + AttResBase attResBase = attResBaseService.list().get(0); + OsmoticQuerySo querySo = new OsmoticQuerySo(); + querySo.setDateTimeRangeSo(dto.getDateTimeRangeSo()); + List stRzVos = baseMapper.qeury8AmRz(querySo,attResBase.getStcd()); + Map rzMap = stRzVos.stream() + .collect(Collectors.toMap( + StRzVo::getTm, // key: 时间 + StRzVo::getRz, // value: 水位 + (existing, replacement) -> existing // 如果key冲突,保留已存在的 + )); + //组合成为 时间 库水位 管道水位的数据 + List data = new ArrayList<>(); + for (JcskSyREightAm eightAm : jcskSyREightAms) { + OsmoticPressDetailVo detailVo = new OsmoticPressDetailVo(); + + // 设置监测时间和监测值(管道水位) + String mstmStr = eightAm.getMstm().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + detailVo.setTm(mstmStr); + detailVo.setValue(eightAm.getSpprwl()); + + // 查找对应的库水位 + BigDecimal rzValue = findClosestRzValue(rzMap, mstmStr); + detailVo.setRz(rzValue); + data.add(detailVo); + } + if(data.isEmpty()){ + return res; + } + RegressionEquation first = RegressionAnalysis.calculateLinear(data); + RegressionEquation second = RegressionAnalysis.calculateQuadratic(data); + RegressionEquation three = RegressionAnalysis.calculateCubic(data); + RegressionEquation four = RegressionAnalysis.calculateQuartic(data); + res.put("first", first); + res.put("second", second); + res.put("three", three); + res.put("four", four); + return res; + } + + /** + * 查找最接近的库水位值 + */ + private BigDecimal findClosestRzValue(Map rzMap, String targetTimeStr) { + // 如果直接匹配到,直接返回 + if (rzMap.containsKey(targetTimeStr)) { + return rzMap.get(targetTimeStr); + } + + // 如果没有直接匹配,查找最接近的时间 + LocalDateTime targetTime = LocalDateTime.parse(targetTimeStr, + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + String closestTime = null; + long minDiff = Long.MAX_VALUE; + + for (String timeStr : rzMap.keySet()) { + LocalDateTime time = LocalDateTime.parse(timeStr, + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + long diff = Math.abs(Duration.between(targetTime, time).toMinutes()); + + if (diff < minDiff) { + minDiff = diff; + closestTime = timeStr; + } + } + + return closestTime != null ? rzMap.get(closestTime) : BigDecimal.ZERO; + } } diff --git a/src/main/java/com/gunshi/project/hsz/service/SyDataCheckRuleService.java b/src/main/java/com/gunshi/project/hsz/service/SyDataCheckRuleService.java new file mode 100644 index 0000000..359f993 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/service/SyDataCheckRuleService.java @@ -0,0 +1,54 @@ +package com.gunshi.project.hsz.service; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gunshi.project.hsz.common.model.JcskSyB; +import com.gunshi.project.hsz.entity.so.SyDataCheckRulePageSo; +import com.gunshi.project.hsz.mapper.StZvarlBMapper; +import com.gunshi.project.hsz.mapper.SyDataCheckRuleMapper; +import com.gunshi.project.hsz.model.StZvarlB; +import com.gunshi.project.hsz.model.SyDataCheckRule; +import com.ruoyi.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@Transactional(rollbackFor = Exception.class) +public class SyDataCheckRuleService extends ServiceImpl { + + public Page pageQuery(SyDataCheckRulePageSo page) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if(!StringUtils.isBlank(page.getDvcd())){ + queryWrapper.eq(SyDataCheckRule::getDvcd,page.getDvcd()); + } + Page syDataCheckRulePage = this.baseMapper.selectPage(page.getPageSo().toPage(), queryWrapper); + return syDataCheckRulePage; + } + + public SyDataCheckRule saveData(SyDataCheckRule dto) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SyDataCheckRule::getDvcd,dto.getDvcd()); + SyDataCheckRule syDataCheckRule = this.baseMapper.selectOne(queryWrapper); + if(syDataCheckRule != null){ + throw new IllegalArgumentException("对不起,该测点已配置清洗策略"); + } + save(dto); + return dto; + } + + public Boolean updateData(SyDataCheckRule dto) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SyDataCheckRule::getDvcd,dto.getDvcd()); + queryWrapper.ne(SyDataCheckRule::getId,dto.getId()); + SyDataCheckRule syDataCheckRule = this.baseMapper.selectOne(queryWrapper); + if(syDataCheckRule != null){ + throw new IllegalArgumentException("对不起,该测点已配置清洗策略"); + } + boolean flag = updateById(dto); + return flag; + } +} diff --git a/src/main/java/com/gunshi/project/hsz/service/SyRegressionDataService.java b/src/main/java/com/gunshi/project/hsz/service/SyRegressionDataService.java new file mode 100644 index 0000000..b1aeb50 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/service/SyRegressionDataService.java @@ -0,0 +1,33 @@ +package com.gunshi.project.hsz.service; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gunshi.project.hsz.mapper.StZvarlBMapper; +import com.gunshi.project.hsz.mapper.SyRegressionDataMapper; +import com.gunshi.project.hsz.model.StZvarlB; +import com.gunshi.project.hsz.model.SyRegressionData; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Slf4j +@Transactional(rollbackFor = Exception.class) +public class SyRegressionDataService extends ServiceImpl { + + + public List queryData(SyRegressionData dto) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if(dto.getDvcd() != null){ + queryWrapper.eq(SyRegressionData::getDvcd, dto.getDvcd()); + } + if(dto.getOrder() != null){ + queryWrapper.eq(SyRegressionData::getOrder, dto.getOrder()); + } + List syRegressionData = this.baseMapper.selectList(queryWrapper); + return syRegressionData; + } +} diff --git a/src/main/java/com/gunshi/project/hsz/util/ProjectCalculateUtil.java b/src/main/java/com/gunshi/project/hsz/util/ProjectCalculateUtil.java new file mode 100644 index 0000000..e2238a6 --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/util/ProjectCalculateUtil.java @@ -0,0 +1,204 @@ +package com.gunshi.project.hsz.util; + +import com.gunshi.project.hsz.common.model.vo.OsmoticPressDetailVo; +import com.gunshi.project.hsz.model.RegressionEquation; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 工程安全分析-线性回归方程计算 + */ +public class ProjectCalculateUtil { + + private static List filterValidData(List data) { + return data.stream() + .filter(vo -> vo.getRz() != null && vo.getValue() != null) + .collect(Collectors.toList()); + } + + private static BigDecimal calculateRSquared(List data, + List coefficients, + BigDecimal meanY) { + BigDecimal ssTot = BigDecimal.ZERO; + BigDecimal ssRes = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : data) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + BigDecimal yPredicted = predictWithCoefficients(x, coefficients); + + BigDecimal diffActual = y.subtract(meanY); + ssTot = ssTot.add(diffActual.multiply(diffActual)); + + BigDecimal diffResidual = y.subtract(yPredicted); + ssRes = ssRes.add(diffResidual.multiply(diffResidual)); + } + + if (ssTot.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ONE; + } + + return BigDecimal.ONE.subtract(ssRes.divide(ssTot, 30, RoundingMode.HALF_UP)); + } + + private static BigDecimal predictWithCoefficients(BigDecimal x, List coefficients) { + BigDecimal result = BigDecimal.ZERO; + BigDecimal xPower = BigDecimal.ONE; + + for (BigDecimal coeff : coefficients) { + result = result.add(coeff.multiply(xPower)); + xPower = xPower.multiply(x); + } + + return result; + } + + private static BigDecimal calculateDeterminant3x3(BigDecimal a11, BigDecimal a12, BigDecimal a13, + BigDecimal a21, BigDecimal a22, BigDecimal a23, + BigDecimal a31, BigDecimal a32, BigDecimal a33) { + return a11.multiply(a22.multiply(a33).subtract(a23.multiply(a32))) + .subtract(a12.multiply(a21.multiply(a33).subtract(a23.multiply(a31)))) + .add(a13.multiply(a21.multiply(a32).subtract(a22.multiply(a31)))); + } + + /** + * 计算线性回归方程(1阶) 库水位x轴,检测值y轴 + */ + public static RegressionEquation calculateLinear(List data) { + List validData = filterValidData(data); + if (validData.isEmpty()) { + throw new IllegalArgumentException("没有有效的数据点进行计算"); + } + + int n = validData.size(); + + BigDecimal sumX = BigDecimal.ZERO; + BigDecimal sumY = BigDecimal.ZERO; + BigDecimal sumXY = BigDecimal.ZERO; + BigDecimal sumX2 = BigDecimal.ZERO; + BigDecimal sumY2 = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : validData) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + + sumX = sumX.add(x); + sumY = sumY.add(y); + sumXY = sumXY.add(x.multiply(y)); + sumX2 = sumX2.add(x.multiply(x)); + sumY2 = sumY2.add(y.multiply(y)); + } + + BigDecimal nBig = new BigDecimal(n); + BigDecimal meanX = sumX.divide(nBig, 30, RoundingMode.HALF_UP); + BigDecimal meanY = sumY.divide(nBig, 30, RoundingMode.HALF_UP); + + BigDecimal numerator = sumXY.subtract(nBig.multiply(meanX).multiply(meanY)); + BigDecimal denominator = sumX2.subtract(nBig.multiply(meanX).multiply(meanX)); + + if (denominator.compareTo(BigDecimal.ZERO) == 0) { + throw new IllegalArgumentException("数据点x值相同,无法计算斜率"); + } + + BigDecimal slope = numerator.divide(denominator, 30, RoundingMode.HALF_UP); + BigDecimal intercept = meanY.subtract(slope.multiply(meanX)); + + // 计算R² + BigDecimal rSquared = calculateRSquared(validData, + List.of(intercept, slope), meanY); + + // 系数列表:[常数项, 一次项系数] + List coefficients = List.of( + intercept.setScale(10, RoundingMode.HALF_UP), + slope.setScale(10, RoundingMode.HALF_UP) + ); + + return new RegressionEquation(1, coefficients, rSquared, n); + } + + /** + * 计算二次回归方程(2阶) + */ + public static RegressionEquation calculateQuadratic(List data) { + List validData = filterValidData(data); + if (validData.size() < 3) { + throw new IllegalArgumentException("二次回归至少需要3个数据点"); + } + + int n = validData.size(); + + BigDecimal sumX = BigDecimal.ZERO, sumY = BigDecimal.ZERO; + BigDecimal sumX2 = BigDecimal.ZERO, sumX3 = BigDecimal.ZERO, sumX4 = BigDecimal.ZERO; + BigDecimal sumXY = BigDecimal.ZERO, sumX2Y = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : validData) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + + BigDecimal x2 = x.multiply(x); + BigDecimal x3 = x2.multiply(x); + BigDecimal x4 = x3.multiply(x); + + sumX = sumX.add(x); + sumY = sumY.add(y); + sumX2 = sumX2.add(x2); + sumX3 = sumX3.add(x3); + sumX4 = sumX4.add(x4); + sumXY = sumXY.add(x.multiply(y)); + sumX2Y = sumX2Y.add(x2.multiply(y)); + } + + BigDecimal nBig = new BigDecimal(n); + + // 使用克莱姆法则求解 + BigDecimal det = calculateDeterminant3x3( + nBig, sumX, sumX2, + sumX, sumX2, sumX3, + sumX2, sumX3, sumX4 + ); + + if (det.compareTo(BigDecimal.ZERO) == 0) { + throw new IllegalArgumentException("矩阵奇异,无法求解二次回归方程"); + } + + BigDecimal detA0 = calculateDeterminant3x3( + sumY, sumX, sumX2, + sumXY, sumX2, sumX3, + sumX2Y, sumX3, sumX4 + ); + + BigDecimal detA1 = calculateDeterminant3x3( + nBig, sumY, sumX2, + sumX, sumXY, sumX3, + sumX2, sumX2Y, sumX4 + ); + + BigDecimal detA2 = calculateDeterminant3x3( + nBig, sumX, sumY, + sumX, sumX2, sumXY, + sumX2, sumX3, sumX2Y + ); + + BigDecimal a0 = detA0.divide(det, 30, RoundingMode.HALF_UP); + BigDecimal a1 = detA1.divide(det, 30, RoundingMode.HALF_UP); + BigDecimal a2 = detA2.divide(det, 30, RoundingMode.HALF_UP); + + // 计算R² + BigDecimal meanY = sumY.divide(nBig, 30, RoundingMode.HALF_UP); + BigDecimal rSquared = calculateRSquared(validData, + List.of(a0, a1, a2), meanY); + + // 系数列表:[常数项, 一次项系数, 二次项系数] + List coefficients = List.of( + a0.setScale(10, RoundingMode.HALF_UP), + a1.setScale(10, RoundingMode.HALF_UP), + a2.setScale(10, RoundingMode.HALF_UP) + ); + + return new RegressionEquation(2, coefficients, rSquared, n); + } + +} diff --git a/src/main/java/com/gunshi/project/hsz/util/RegressionAnalysis.java b/src/main/java/com/gunshi/project/hsz/util/RegressionAnalysis.java new file mode 100644 index 0000000..b66b3dd --- /dev/null +++ b/src/main/java/com/gunshi/project/hsz/util/RegressionAnalysis.java @@ -0,0 +1,581 @@ +package com.gunshi.project.hsz.util; + +import com.gunshi.project.hsz.common.model.vo.OsmoticPressDetailVo; +import com.gunshi.project.hsz.model.RegressionEquation; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 回归分析工具类 + */ +public class RegressionAnalysis { + + /** + * 计算线性回归方程(1阶) + */ + public static RegressionEquation calculateLinear(List data) { + int n = 0; + BigDecimal rSquared = null; + List coefficients = null; + try { + List validData = filterValidData(data); + if (validData.isEmpty()) { + throw new IllegalArgumentException("没有有效的数据点进行计算"); + } + + n = validData.size(); + + BigDecimal sumX = BigDecimal.ZERO; + BigDecimal sumY = BigDecimal.ZERO; + BigDecimal sumXY = BigDecimal.ZERO; + BigDecimal sumX2 = BigDecimal.ZERO; + BigDecimal sumY2 = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : validData) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + + sumX = sumX.add(x); + sumY = sumY.add(y); + sumXY = sumXY.add(x.multiply(y)); + sumX2 = sumX2.add(x.multiply(x)); + sumY2 = sumY2.add(y.multiply(y)); + } + + BigDecimal nBig = new BigDecimal(n); + BigDecimal meanX = sumX.divide(nBig, 100, RoundingMode.HALF_UP); + BigDecimal meanY = sumY.divide(nBig, 100, RoundingMode.HALF_UP); + + BigDecimal numerator = sumXY.subtract(nBig.multiply(meanX).multiply(meanY)); + BigDecimal denominator = sumX2.subtract(nBig.multiply(meanX).multiply(meanX)); + + if (denominator.compareTo(BigDecimal.ZERO) == 0) { + throw new IllegalArgumentException("数据点x值相同,无法计算斜率"); + } + + BigDecimal slope = numerator.divide(denominator, 100, RoundingMode.HALF_UP); + BigDecimal intercept = meanY.subtract(slope.multiply(meanX)); + + // 计算R² + rSquared = calculateRSquared(validData, List.of(intercept, slope), meanY); + + // 系数列表:[常数项, 一次项系数] - 不进行四舍五入 + coefficients = List.of(intercept, slope); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return null; + } + + return new RegressionEquation(1, coefficients, rSquared, n); + } + + /** + * 计算二次回归方程(2阶) + */ + public static RegressionEquation calculateQuadratic(List data) { + int n = 0; + BigDecimal rSquared = null; + List coefficients = null; + try { + List validData = filterValidData(data); + if (validData.size() < 3) { + throw new IllegalArgumentException("二次回归至少需要3个数据点"); + } + + n = validData.size(); + + // 计算各项和 + BigDecimal sumX = BigDecimal.ZERO, sumY = BigDecimal.ZERO; + BigDecimal sumX2 = BigDecimal.ZERO, sumX3 = BigDecimal.ZERO, sumX4 = BigDecimal.ZERO; + BigDecimal sumXY = BigDecimal.ZERO, sumX2Y = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : validData) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + + BigDecimal x2 = x.multiply(x); + BigDecimal x3 = x2.multiply(x); + BigDecimal x4 = x3.multiply(x); + + sumX = sumX.add(x); + sumY = sumY.add(y); + sumX2 = sumX2.add(x2); + sumX3 = sumX3.add(x3); + sumX4 = sumX4.add(x4); + sumXY = sumXY.add(x.multiply(y)); + sumX2Y = sumX2Y.add(x2.multiply(y)); + } + + BigDecimal nBig = new BigDecimal(n); + + // 使用克莱姆法则求解 + BigDecimal det = calculateDeterminant3x3( + nBig, sumX, sumX2, + sumX, sumX2, sumX3, + sumX2, sumX3, sumX4 + ); + + if (det.compareTo(BigDecimal.ZERO) == 0) { + throw new IllegalArgumentException("矩阵奇异,无法求解二次回归方程"); + } + + // 求解常数项 a + BigDecimal detA = calculateDeterminant3x3( + sumY, sumX, sumX2, + sumXY, sumX2, sumX3, + sumX2Y, sumX3, sumX4 + ); + + // 求解一次项系数 b + BigDecimal detB = calculateDeterminant3x3( + nBig, sumY, sumX2, + sumX, sumXY, sumX3, + sumX2, sumX2Y, sumX4 + ); + + // 求解二次项系数 c + BigDecimal detC = calculateDeterminant3x3( + nBig, sumX, sumY, + sumX, sumX2, sumXY, + sumX2, sumX3, sumX2Y + ); + + BigDecimal a = detA.divide(det, 100, RoundingMode.HALF_UP); // 常数项 + BigDecimal b = detB.divide(det, 100, RoundingMode.HALF_UP); // 一次项系数 + BigDecimal c = detC.divide(det, 100, RoundingMode.HALF_UP); // 二次项系数 + + // 计算R² + BigDecimal meanY = sumY.divide(nBig, 100, RoundingMode.HALF_UP); + rSquared = calculateRSquared(validData, List.of(a, b, c), meanY); + + // 系数列表:[常数项, 一次项系数, 二次项系数] - 不进行四舍五入 + coefficients = List.of(a, b, c); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return null; + } + + return new RegressionEquation(2, coefficients, rSquared, n); + } + + /** + * 计算三次回归方程(3阶)- 优化版本 + */ + public static RegressionEquation calculateCubic(List data) { + int n = 0; + List coefficientsList = null; + BigDecimal rSquared = null; + try { + List validData = filterValidData(data); + if (validData.size() < 4) { + throw new IllegalArgumentException("三次回归至少需要4个数据点"); + } + + n = validData.size(); + + // 使用中心化数据提高数值稳定性 + BigDecimal meanX = calculateMean(validData, OsmoticPressDetailVo::getRz); + + // 计算各项和(使用中心化数据) + BigDecimal sumX = BigDecimal.ZERO, sumY = BigDecimal.ZERO; + BigDecimal sumX2 = BigDecimal.ZERO, sumX3 = BigDecimal.ZERO, sumX4 = BigDecimal.ZERO; + BigDecimal sumX5 = BigDecimal.ZERO, sumX6 = BigDecimal.ZERO; + BigDecimal sumXY = BigDecimal.ZERO, sumX2Y = BigDecimal.ZERO, sumX3Y = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : validData) { + BigDecimal x = vo.getRz().subtract(meanX); // 中心化 + BigDecimal y = vo.getValue(); + + BigDecimal x2 = x.multiply(x); + BigDecimal x3 = x2.multiply(x); + BigDecimal x4 = x3.multiply(x); + BigDecimal x5 = x4.multiply(x); + BigDecimal x6 = x5.multiply(x); + + sumX = sumX.add(x); + sumY = sumY.add(y); + sumX2 = sumX2.add(x2); + sumX3 = sumX3.add(x3); + sumX4 = sumX4.add(x4); + sumX5 = sumX5.add(x5); + sumX6 = sumX6.add(x6); + sumXY = sumXY.add(x.multiply(y)); + sumX2Y = sumX2Y.add(x2.multiply(y)); + sumX3Y = sumX3Y.add(x3.multiply(y)); + } + + BigDecimal nBig = new BigDecimal(n); + + // 构建正规方程组(中心化后的矩阵条件数更好) + BigDecimal[][] matrix = { + {nBig, sumX, sumX2, sumX3}, + {sumX, sumX2, sumX3, sumX4}, + {sumX2, sumX3, sumX4, sumX5}, + {sumX3, sumX4, sumX5, sumX6} + }; + + BigDecimal[] vector = {sumY, sumXY, sumX2Y, sumX3Y}; + + // 使用改进的高斯消元法 + BigDecimal[] centeredCoefficients = solveLinearSystemImproved(matrix, vector); + + if (centeredCoefficients == null) { + throw new IllegalArgumentException("无法求解三次回归方程"); + } + + // 将中心化系数转换回原始坐标 + BigDecimal[] originalCoefficients = convertToOriginalCoefficients(centeredCoefficients, meanX); + + // 计算R² + BigDecimal meanY = sumY.divide(nBig, MathContext.DECIMAL128); + coefficientsList = Arrays.asList(originalCoefficients); + rSquared = calculateRSquared(validData, coefficientsList, meanY); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return null; + } + + return new RegressionEquation(3, coefficientsList, rSquared, n); + } + + /** + * 将中心化系数转换回原始坐标 + */ + private static BigDecimal[] convertToOriginalCoefficients(BigDecimal[] centeredCoeffs, BigDecimal meanX) { + // 对于三次多项式:y = a + b(x - μ) + c(x - μ)² + d(x - μ)³ + // 展开后:y = (a - bμ + cμ² - dμ³) + (b - 2cμ + 3dμ²)x + (c - 3dμ)x² + d x³ + + BigDecimal a = centeredCoeffs[0]; + BigDecimal b = centeredCoeffs[1]; + BigDecimal c = centeredCoeffs[2]; + BigDecimal d = centeredCoeffs[3]; + + BigDecimal mu = meanX; + BigDecimal mu2 = mu.multiply(mu); + BigDecimal mu3 = mu2.multiply(mu); + + BigDecimal newA = a.subtract(b.multiply(mu)) + .add(c.multiply(mu2)) + .subtract(d.multiply(mu3)); + + BigDecimal newB = b.subtract(BigDecimal.valueOf(2).multiply(c).multiply(mu)) + .add(BigDecimal.valueOf(3).multiply(d).multiply(mu2)); + + BigDecimal newC = c.subtract(BigDecimal.valueOf(3).multiply(d).multiply(mu)); + BigDecimal newD = d; + + return new BigDecimal[]{newA, newB, newC, newD}; + } + + /** + * 带完全主元的高斯消元法,提高数值稳定性 + */ + private static BigDecimal[] solveLinearSystemWithPivot(BigDecimal[][] matrix, BigDecimal[] vector) { + int n = vector.length; + + BigDecimal[][] a = new BigDecimal[n][n]; + BigDecimal[] b = new BigDecimal[n]; + for (int i = 0; i < n; i++) { + System.arraycopy(matrix[i], 0, a[i], 0, n); + b[i] = vector[i]; + } + + int[] rowPerm = new int[n]; + int[] colPerm = new int[n]; + for (int i = 0; i < n; i++) { + rowPerm[i] = i; + colPerm[i] = i; + } + + // 完全主元高斯消元 + for (int k = 0; k < n; k++) { + // 寻找主元 + int maxRow = k, maxCol = k; + BigDecimal maxVal = a[rowPerm[k]][colPerm[k]].abs(); + + for (int i = k; i < n; i++) { + for (int j = k; j < n; j++) { + BigDecimal current = a[rowPerm[i]][colPerm[j]].abs(); + if (current.compareTo(maxVal) > 0) { + maxVal = current; + maxRow = i; + maxCol = j; + } + } + } + + // 交换行 + if (maxRow != k) { + int temp = rowPerm[k]; + rowPerm[k] = rowPerm[maxRow]; + rowPerm[maxRow] = temp; + } + + // 交换列 + if (maxCol != k) { + int temp = colPerm[k]; + colPerm[k] = colPerm[maxCol]; + colPerm[maxCol] = temp; + } + + // 主元为0,矩阵奇异 + if (a[rowPerm[k]][colPerm[k]].compareTo(BigDecimal.ZERO) == 0) { + return null; + } + + // 消元 + for (int i = k + 1; i < n; i++) { + BigDecimal factor = a[rowPerm[i]][colPerm[k]].divide(a[rowPerm[k]][colPerm[k]], 100, RoundingMode.HALF_UP); + for (int j = k; j < n; j++) { + a[rowPerm[i]][colPerm[j]] = a[rowPerm[i]][colPerm[j]].subtract( + factor.multiply(a[rowPerm[k]][colPerm[j]])); + } + b[rowPerm[i]] = b[rowPerm[i]].subtract(factor.multiply(b[rowPerm[k]])); + } + } + + // 回代 + BigDecimal[] x = new BigDecimal[n]; + for (int i = n - 1; i >= 0; i--) { + BigDecimal sum = BigDecimal.ZERO; + for (int j = i + 1; j < n; j++) { + sum = sum.add(a[rowPerm[i]][colPerm[j]].multiply(x[colPerm[j]])); + } + x[colPerm[i]] = b[rowPerm[i]].subtract(sum).divide(a[rowPerm[i]][colPerm[i]], 100, RoundingMode.HALF_UP); + } + + // 恢复原始顺序 + BigDecimal[] result = new BigDecimal[n]; + for (int i = 0; i < n; i++) { + result[i] = x[i]; + } + + return result; + } + + + // 其他辅助方法 + private static List filterValidData(List data) { + return data.stream() + .filter(vo -> vo.getRz() != null && vo.getValue() != null) + .collect(Collectors.toList()); + } + + private static BigDecimal calculateDeterminant3x3(BigDecimal a11, BigDecimal a12, BigDecimal a13, + BigDecimal a21, BigDecimal a22, BigDecimal a23, + BigDecimal a31, BigDecimal a32, BigDecimal a33) { + return a11.multiply(a22.multiply(a33).subtract(a23.multiply(a32))) + .subtract(a12.multiply(a21.multiply(a33).subtract(a23.multiply(a31)))) + .add(a13.multiply(a21.multiply(a32).subtract(a22.multiply(a31)))); + } + + + private static BigDecimal predictWithCoefficients(BigDecimal x, List coefficients) { + BigDecimal result = BigDecimal.ZERO; + BigDecimal xPower = BigDecimal.ONE; + + for (BigDecimal coeff : coefficients) { + result = result.add(coeff.multiply(xPower)); + xPower = xPower.multiply(x); + } + + return result; + } + + + /** + * 计算四次回归方程(4阶) + */ + public static RegressionEquation calculateQuartic(List data) { + int n = 0; + List coeffsList = null; + BigDecimal rSquared = null; + try { + List validData = filterValidData(data); + if (validData.size() < 5) { + throw new IllegalArgumentException("四次回归至少需要5个数据点"); + } + + n = validData.size(); + + // 计算各项和:sumX, sumY, sumX2, sumX3, sumX4, sumX5, sumX6, sumX7, sumX8, sumXY, sumX2Y, sumX3Y, sumX4Y + BigDecimal sumX = BigDecimal.ZERO, sumY = BigDecimal.ZERO; + BigDecimal sumX2 = BigDecimal.ZERO, sumX3 = BigDecimal.ZERO, sumX4 = BigDecimal.ZERO; + BigDecimal sumX5 = BigDecimal.ZERO, sumX6 = BigDecimal.ZERO, sumX7 = BigDecimal.ZERO, sumX8 = BigDecimal.ZERO; + BigDecimal sumXY = BigDecimal.ZERO, sumX2Y = BigDecimal.ZERO, sumX3Y = BigDecimal.ZERO, sumX4Y = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : validData) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + + BigDecimal x2 = x.multiply(x); + BigDecimal x3 = x2.multiply(x); + BigDecimal x4 = x3.multiply(x); + BigDecimal x5 = x4.multiply(x); + BigDecimal x6 = x5.multiply(x); + BigDecimal x7 = x6.multiply(x); + BigDecimal x8 = x7.multiply(x); + + sumX = sumX.add(x); + sumY = sumY.add(y); + sumX2 = sumX2.add(x2); + sumX3 = sumX3.add(x3); + sumX4 = sumX4.add(x4); + sumX5 = sumX5.add(x5); + sumX6 = sumX6.add(x6); + sumX7 = sumX7.add(x7); + sumX8 = sumX8.add(x8); + sumXY = sumXY.add(x.multiply(y)); + sumX2Y = sumX2Y.add(x2.multiply(y)); + sumX3Y = sumX3Y.add(x3.multiply(y)); + sumX4Y = sumX4Y.add(x4.multiply(y)); + } + + BigDecimal nBig = new BigDecimal(n); + + // 构建正规方程组的系数矩阵 + BigDecimal[][] matrix = { + {nBig, sumX, sumX2, sumX3, sumX4}, // 第1行 + {sumX, sumX2, sumX3, sumX4, sumX5}, // 第2行 + {sumX2, sumX3, sumX4, sumX5, sumX6}, // 第3行 + {sumX3, sumX4, sumX5, sumX6, sumX7}, // 第4行 + {sumX4, sumX5, sumX6, sumX7, sumX8} // 第5行 + }; + + // 构建右侧向量 + BigDecimal[] vector = {sumY, sumXY, sumX2Y, sumX3Y, sumX4Y}; + + // 使用改进的高斯消元法求解系数 [a, b, c, d, e] + BigDecimal[] coefficients = solveLinearSystemImproved(matrix, vector); + if (coefficients == null) { + throw new IllegalArgumentException("无法求解四次回归方程(矩阵奇异)"); + } + + // 计算R² + BigDecimal meanY = sumY.divide(nBig, MathContext.DECIMAL128); + coeffsList = Arrays.asList(coefficients); + rSquared = calculateRSquared(validData, coeffsList, meanY); + } catch (IllegalArgumentException e) { + return null; + } + + return new RegressionEquation(4, coeffsList, rSquared, n); + } + + /** + * 改进的线性方程组求解器(部分主元高斯消元) + * 用于解决n元一次方程组 Ax = b + */ + private static BigDecimal[] solveLinearSystemImproved(BigDecimal[][] matrix, BigDecimal[] vector) { + int n = vector.length; + + // 复制矩阵和向量(避免修改原数据) + BigDecimal[][] a = new BigDecimal[n][n]; + BigDecimal[] b = new BigDecimal[n]; + for (int i = 0; i < n; i++) { + a[i] = matrix[i].clone(); + b[i] = vector[i]; + } + + // 部分主元高斯消元:逐行处理,选择当前列的最大绝对值元素作为主元 + for (int k = 0; k < n; k++) { + // 找到当前列(k列)中绝对值最大的行(maxRow) + int maxRow = k; + BigDecimal maxVal = a[k][k].abs(); // 初始为当前对角线元素 + for (int i = k + 1; i < n; i++) { + BigDecimal current = a[i][k].abs(); + if (current.compareTo(maxVal) > 0) { + maxVal = current; + maxRow = i; + } + } + + // 交换行(将最大主元所在行交换到当前行k) + if (maxRow != k) { + BigDecimal[] tempRow = a[k]; + a[k] = a[maxRow]; + a[maxRow] = tempRow; + + BigDecimal tempB = b[k]; + b[k] = b[maxRow]; + b[maxRow] = tempB; + } + + // 检查主元是否为0(矩阵奇异) + if (a[k][k].compareTo(BigDecimal.ZERO) == 0) { + return null; + } + + // 消元:将第k列下方的元素消为0 + for (int i = k + 1; i < n; i++) { + BigDecimal factor = a[i][k].divide(a[k][k], MathContext.DECIMAL128); // 除法保留高精度 + for (int j = k; j < n; j++) { + a[i][j] = a[i][j].subtract(factor.multiply(a[k][j], MathContext.DECIMAL128), MathContext.DECIMAL128); + } + b[i] = b[i].subtract(factor.multiply(b[k], MathContext.DECIMAL128), MathContext.DECIMAL128); + } + } + + // 回代:从最后一行开始,依次求解每个变量 + BigDecimal[] x = new BigDecimal[n]; + for (int i = n - 1; i >= 0; i--) { + BigDecimal sum = BigDecimal.ZERO; + for (int j = i + 1; j < n; j++) { + sum = sum.add(a[i][j].multiply(x[j], MathContext.DECIMAL128), MathContext.DECIMAL128); + } + x[i] = b[i].subtract(sum, MathContext.DECIMAL128) + .divide(a[i][i], MathContext.DECIMAL128); // 除法保留高精度 + } + + return x; + } + + // ========== 辅助方法(已存在,无需修改) ========== + + /** + * 计算均值(高精度) + */ + private static BigDecimal calculateMean(List data, + java.util.function.Function extractor) { + BigDecimal sum = BigDecimal.ZERO; + for (OsmoticPressDetailVo vo : data) { + sum = sum.add(extractor.apply(vo)); + } + return sum.divide(new BigDecimal(data.size()), MathContext.DECIMAL128); + } + + + /** + * 计算R² + */ + private static BigDecimal calculateRSquared(List data, + List coefficients, + BigDecimal meanY) { + BigDecimal ssTot = BigDecimal.ZERO; + BigDecimal ssRes = BigDecimal.ZERO; + + for (OsmoticPressDetailVo vo : data) { + BigDecimal x = vo.getRz(); + BigDecimal y = vo.getValue(); + BigDecimal yPredicted = predictWithCoefficients(x, coefficients); + + BigDecimal diffActual = y.subtract(meanY); + ssTot = ssTot.add(diffActual.multiply(diffActual)); + + BigDecimal diffResidual = y.subtract(yPredicted); + ssRes = ssRes.add(diffResidual.multiply(diffResidual)); + } + + if (ssTot.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ONE; // 总平方和为0,拟合完美 + } + return BigDecimal.ONE.subtract(ssRes.divide(ssTot, MathContext.DECIMAL128), MathContext.DECIMAL128); + } + + +} \ No newline at end of file