Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
HYH.APSJ
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
佟礼
HYH.APSJ
Commits
f11971ca
Commit
f11971ca
authored
Sep 15, 2026
by
Tong Li
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MP
parent
2ec4b057
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
809 additions
and
172 deletions
+809
-172
MacroPlannerResultController.java
...java/com/aps/controller/MacroPlannerResultController.java
+12
-130
MacroPlannerOptimizer.java
...main/java/com/aps/macroplanner/MacroPlannerOptimizer.java
+11
-1
MultiLevelBomTestRunner.java
...in/java/com/aps/macroplanner/MultiLevelBomTestRunner.java
+35
-13
ResultWriter.java
src/main/java/com/aps/macroplanner/output/ResultWriter.java
+171
-28
BomStructureData.java
...ava/com/aps/macroplanner/output/dto/BomStructureData.java
+90
-0
OperationDemandResult.java
...om/aps/macroplanner/output/dto/OperationDemandResult.java
+43
-0
MacroPlannerProductNetworkService.java
...va/com/aps/service/MacroPlannerProductNetworkService.java
+447
-0
No files found.
src/main/java/com/aps/controller/MacroPlannerResultController.java
View file @
f11971ca
...
@@ -13,6 +13,7 @@ import com.aps.macroplanner.data.TestDataBuilder;
...
@@ -13,6 +13,7 @@ import com.aps.macroplanner.data.TestDataBuilder;
import
com.aps.macroplanner.output.ResultWriter
;
import
com.aps.macroplanner.output.ResultWriter
;
import
com.aps.macroplanner.output.dto.*
;
import
com.aps.macroplanner.output.dto.*
;
import
com.aps.service.MacroPlannerResultService
;
import
com.aps.service.MacroPlannerResultService
;
import
com.aps.service.MacroPlannerProductNetworkService
;
import
com.aps.service.MpPispipResultPersistenceService
;
import
com.aps.service.MpPispipResultPersistenceService
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.google.ortools.Loader
;
import
com.google.ortools.Loader
;
...
@@ -67,6 +68,9 @@ public class MacroPlannerResultController {
...
@@ -67,6 +68,9 @@ public class MacroPlannerResultController {
@Autowired
@Autowired
private
MacroPlannerResultService
macroPlannerResultService
;
private
MacroPlannerResultService
macroPlannerResultService
;
@Autowired
private
MacroPlannerProductNetworkService
macroPlannerProductNetworkService
;
@Autowired
@Autowired
private
MacroPlannerDataConverter
macroPlannerDataConverter
;
private
MacroPlannerDataConverter
macroPlannerDataConverter
;
...
@@ -304,11 +308,15 @@ public class MacroPlannerResultController {
...
@@ -304,11 +308,15 @@ public class MacroPlannerResultController {
@RequestParam
(
value
=
"productId"
,
required
=
false
)
@Parameter
(
description
=
"按产品ID过滤成品根节点(可选)"
)
String
productId
,
@RequestParam
(
value
=
"productId"
,
required
=
false
)
@Parameter
(
description
=
"按产品ID过滤成品根节点(可选)"
)
String
productId
,
@RequestParam
(
value
=
"spId"
,
required
=
false
)
@Parameter
(
description
=
"按库存点ID过滤(可选)"
)
String
spId
,
@RequestParam
(
value
=
"spId"
,
required
=
false
)
@Parameter
(
description
=
"按库存点ID过滤(可选)"
)
String
spId
,
@RequestParam
(
value
=
"period"
,
required
=
false
)
@Parameter
(
description
=
"按周期过滤, 仅保留该周期内有供应的节点/来源(可选)"
)
String
period
)
{
@RequestParam
(
value
=
"period"
,
required
=
false
)
@Parameter
(
description
=
"按周期过滤, 仅保留该周期内有供应的节点/来源(可选)"
)
String
period
)
{
OptimizationResult
result
=
loadResult
(
sceneId
);
if
(
productId
==
null
||
productId
.
trim
().
isEmpty
()
||
spId
==
null
||
spId
.
trim
().
isEmpty
())
{
if
(
result
==
null
)
{
return
R
.
failed
(
"productId 和 spId 不能为空"
);
return
R
.
failed
(
"未找到场景 "
+
sceneId
+
" 的排产结果文件"
);
}
SupplyChainNode
node
=
macroPlannerProductNetworkService
.
buildProductNetwork
(
sceneId
,
productId
.
trim
(),
spId
.
trim
(),
period
);
if
(
node
==
null
)
{
return
R
.
failed
(
"未找到场景 "
+
sceneId
+
" 中 "
+
productId
+
"@"
+
spId
+
" 的产品网络"
);
}
}
return
R
.
ok
(
filterProductNetwork
(
result
.
getProductNetwork
(),
productId
,
spId
,
period
)
);
return
R
.
ok
(
node
);
}
}
/**
/**
...
@@ -451,132 +459,6 @@ public class MacroPlannerResultController {
...
@@ -451,132 +459,6 @@ public class MacroPlannerResultController {
return
new
ArrayList
<>(
merged
.
values
());
return
new
ArrayList
<>(
merged
.
values
());
}
}
/**
* 过滤产品生产网络:
* <ul>
* <li>productId / spId — 仅保留匹配的成品根节点及其子树;</li>
* <li>unitId — 仅保留该设备上的供应来源;</li>
* <li>periodIndex — 仅保留该周期内有供应(生产/消耗/库存/销售)的节点与来源, 其余剪枝。</li>
* </ul>
* 无过滤条件时原样返回。
*/
private
SupplyChainNode
filterProductNetwork
(
ProductNetworkResult
network
,
String
productId
,
String
spId
,
String
periodIndex
)
{
if
(
network
==
null
)
{
return
null
;
}
if
(!
hasText
(
productId
)
&&
!
hasText
(
spId
)
&&
periodIndex
==
null
)
{
return
null
;
}
ProductNetworkResult
filtered
=
new
ProductNetworkResult
();
SupplyChainNode
childSrc
=
network
.
getAllNodes
().
get
(
productId
+
"@"
+
spId
);
if
(
childSrc
==
null
)
{
return
null
;
}
SupplyChainNode
childNode
=
copyNetworkNode
(
childSrc
,
network
,
filtered
,
periodIndex
);
if
(
childNode
==
null
)
{
return
null
;
}
return
childNode
;
}
/**
* 递归拷贝网络节点, 同时按设备与周期剪枝供应来源、子节点。
*/
private
SupplyChainNode
copyNetworkNode
(
SupplyChainNode
src
,
ProductNetworkResult
source
,
ProductNetworkResult
target
,
String
periodIndex
)
{
if
(
periodIndex
!=
null
&&
!
src
.
getActivePeriods
().
contains
(
periodIndex
))
{
return
null
;
}
String
key
=
src
.
getProductId
()
+
"@"
+
src
.
getSpId
();
SupplyChainNode
existing
=
target
.
getAllNodes
().
get
(
key
);
if
(
existing
!=
null
)
{
return
existing
;
}
SupplyChainNode
copy
=
target
.
getOrCreateNode
(
src
.
getProductId
(),
src
.
getSpId
(),
src
.
getSpName
());
copy
.
setLevel
(
src
.
getLevel
());
copy
.
setTotalSalesDemand
(
src
.
getTotalSalesDemand
());
copy
.
setTotalSalesFulfilled
(
src
.
getTotalSalesFulfilled
());
copy
.
setTotalDependentDemand
(
src
.
getTotalDependentDemand
());
// copy.setSummary(src.getSummary());
// 供应来源: 按设备 + 周期过滤
for
(
SupplyChainNode
.
SupplySource
ss
:
src
.
getSupplySources
())
{
for
(
Map
.
Entry
<
String
,
SupplyChainNode
.
SupplySourceDetail
>
entry
:
ss
.
productionByPeriod
.
entrySet
())
{
String
period
=
entry
.
getKey
();
// 周期key,对应截图里的 0、3
if
(
period
.
equals
(
periodIndex
))
{
SupplyChainNode
.
SupplySourceDetail
detail
=
entry
.
getValue
();
// 该周期的产能详情
ss
.
production
=
detail
.
production
;
ss
.
unitId
=
detail
.
unitId
;
ss
.
unitName
=
detail
.
unitName
;
ss
.
productionByPeriod
.
clear
();
copy
.
getSupplySources
().
add
(
ss
);
break
;
}
}
}
// 消费者: 按周期过滤
for
(
SupplyChainNode
.
ConsumerInfo
ci
:
src
.
getConsumers
())
{
if
(
periodIndex
!=
null
&&
!
ci
.
consumedByPeriod
.
containsKey
(
periodIndex
))
{
continue
;
}
copy
.
getConsumers
().
add
(
ci
);
}
// BOM 子物料: 按周期剪枝
for
(
SupplyChainNode
.
BomChild
child
:
src
.
getChildren
())
{
if
(
periodIndex
!=
null
&&
!
child
.
consumedByPeriod
.
containsKey
(
periodIndex
))
{
continue
;
}
SupplyChainNode
childSrc
=
source
.
getAllNodes
().
get
(
child
.
productId
+
"@"
+
child
.
spId
);
if
(
childSrc
==
null
)
{
continue
;
}
SupplyChainNode
childNode
=
copyNetworkNode
(
childSrc
,
source
,
target
,
periodIndex
);
if
(
childNode
==
null
)
{
continue
;
// 该子节点在该周期无活动, 剪枝
}
for
(
SupplyChainNode
.
SupplySource
ss
:
childNode
.
getSupplySources
())
{
child
.
totalConsumedQty
=
ss
.
production
;
child
.
unitId
=
ss
.
unitId
;
child
.
unitName
=
ss
.
unitName
;
child
.
consumedByPeriod
.
clear
();
break
;
}
copy
.
getChildren
().
add
(
child
);
}
return
copy
;
}
/**
* 供应来源在指定周期是否有产量 (在途/外部采购视为全周期可用)。
*/
private
boolean
isSourceActiveInPeriod
(
SupplyChainNode
.
SupplySource
ss
,
int
periodIndex
)
{
if
(
"IN_TRANSIT"
.
equals
(
ss
.
type
)
||
"EXTERNAL"
.
equals
(
ss
.
type
))
{
return
true
;
}
return
ss
.
productionByPeriod
.
containsKey
(
periodIndex
);
}
private
void
enrichSalesDemandDisplayFields
(
List
<
SalesDemandResult
>
demands
)
{
private
void
enrichSalesDemandDisplayFields
(
List
<
SalesDemandResult
>
demands
)
{
if
(
demands
==
null
||
demands
.
isEmpty
())
{
if
(
demands
==
null
||
demands
.
isEmpty
())
{
return
;
return
;
...
...
src/main/java/com/aps/macroplanner/MacroPlannerOptimizer.java
View file @
f11971ca
package
com
.
aps
.
macroplanner
;
package
com
.
aps
.
macroplanner
;
import
com.aps.common.util.FileHelper
;
import
com.aps.common.util.FileHelper
;
import
com.aps.macroplanner.output.dto.OptimizationResult
;
import
com.aps.service.MpPispipResultPersistenceService
;
import
com.google.ortools.Loader
;
import
com.google.ortools.Loader
;
import
com.google.ortools.linearsolver.MPSolver
;
import
com.google.ortools.linearsolver.MPSolver
;
import
com.aps.macroplanner.constraint.ConstraintFactory
;
import
com.aps.macroplanner.constraint.ConstraintFactory
;
...
@@ -11,6 +13,7 @@ import com.aps.macroplanner.objective.ObjectiveBuilder;
...
@@ -11,6 +13,7 @@ import com.aps.macroplanner.objective.ObjectiveBuilder;
import
com.aps.macroplanner.objective.StrategyLevel
;
import
com.aps.macroplanner.objective.StrategyLevel
;
import
com.aps.macroplanner.output.ResultWriter
;
import
com.aps.macroplanner.output.ResultWriter
;
import
com.aps.macroplanner.output.SolutionPrinter
;
import
com.aps.macroplanner.output.SolutionPrinter
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
java.io.FileOutputStream
;
import
java.io.FileOutputStream
;
import
java.io.PrintStream
;
import
java.io.PrintStream
;
...
@@ -83,6 +86,8 @@ import java.util.logging.Logger;
...
@@ -83,6 +86,8 @@ import java.util.logging.Logger;
*/
*/
public
class
MacroPlannerOptimizer
{
public
class
MacroPlannerOptimizer
{
/** LP 模型文件和日志文件的输出目录 */
/** LP 模型文件和日志文件的输出目录 */
private
static
final
String
LOG_DIR
=
"mp"
;
private
static
final
String
LOG_DIR
=
"mp"
;
...
@@ -382,7 +387,12 @@ public class MacroPlannerOptimizer {
...
@@ -382,7 +387,12 @@ public class MacroPlannerOptimizer {
// 回写业务对象到JSON文件(预留)
// 回写业务对象到JSON文件(预留)
ResultWriter
rw
=
new
ResultWriter
(
model
,
data
,
startTimeMs
);
ResultWriter
rw
=
new
ResultWriter
(
model
,
data
,
startTimeMs
);
boolean
jsonPath
=
rw
.
saveResultToFile
(
sceneId
);
OptimizationResult
optimizationResult
=
rw
.
buildResult
();
boolean
jsonPath
=
rw
.
saveResultToFile
(
sceneId
,
optimizationResult
);
if
(
jsonPath
)
{
if
(
jsonPath
)
{
writeLog
(
"\n[OK] 优化结果JSON已导出: "
+
jsonPath
);
writeLog
(
"\n[OK] 优化结果JSON已导出: "
+
jsonPath
);
}
}
...
...
src/main/java/com/aps/macroplanner/MultiLevelBomTestRunner.java
View file @
f11971ca
package
com
.
aps
.
macroplanner
;
package
com
.
aps
.
macroplanner
;
import
com.aps.ApsApplication
;
import
com.aps.common.util.FileHelper
;
import
com.aps.common.util.FileHelper
;
import
com.aps.macroplanner.data.BenchmarkDataBuilder
;
import
com.aps.macroplanner.data.BenchmarkDataBuilder
;
import
com.aps.macroplanner.data.LargeScaleBomTestDataBuilder
;
import
com.aps.macroplanner.data.LargeScaleBomTestDataBuilder
;
import
com.aps.macroplanner.output.ResultWriter
;
import
com.aps.macroplanner.output.dto.OptimizationResult
;
import
com.aps.service.MpPispipResultPersistenceService
;
import
com.google.ortools.Loader
;
import
com.google.ortools.Loader
;
import
com.aps.macroplanner.data.MultiLevelBomTestDataBuilder
;
import
com.aps.macroplanner.data.MultiLevelBomTestDataBuilder
;
import
com.aps.macroplanner.data.TestDataBuilder
;
import
com.aps.macroplanner.data.TestDataBuilder
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.context.ApplicationContext
;
/**
/**
* 多级BOM + 多成品 + 共享半成品 测试运行器。
* 多级BOM + 多成品 + 共享半成品 测试运行器。
...
@@ -26,6 +32,7 @@ import com.aps.macroplanner.data.TestDataBuilder;
...
@@ -26,6 +32,7 @@ import com.aps.macroplanner.data.TestDataBuilder;
* </ol>
* </ol>
*/
*/
public
class
MultiLevelBomTestRunner
{
public
class
MultiLevelBomTestRunner
{
private
static
final
String
LOG_DIR
=
"mp"
;
private
static
final
String
LOG_DIR
=
"mp"
;
/** LP 模型文件路径 */
/** LP 模型文件路径 */
...
@@ -46,29 +53,44 @@ public class MultiLevelBomTestRunner {
...
@@ -46,29 +53,44 @@ public class MultiLevelBomTestRunner {
System
.
out
.
println
(
"Demand: P1=40/day, P2=30/day"
);
System
.
out
.
println
(
"Demand: P1=40/day, P2=30/day"
);
System
.
out
.
println
();
System
.
out
.
println
();
// 通过 Spring 容器获取 service 实例 (static main 中 @Autowired 不会生效)
ApplicationContext
ctx
=
SpringApplication
.
run
(
ApsApplication
.
class
,
args
);
try
{
MultiLevelBomTestDataBuilder
data
=
new
MultiLevelBomTestDataBuilder
();
data
.
init
();
MultiLevelBomTestDataBuilder
builder
=
new
MultiLevelBomTestDataBuilder
();
MacroPlannerOptimizer
optimizer
=
new
MacroPlannerOptimizer
(
data
);
builder
.
init
();
MacroPlannerOptimizer
optimizer
=
new
MacroPlannerOptimizer
(
builder
);
optimizer
.
buildModel
();
optimizer
.
buildModel
();
optimizer
.
solve
(
"bom"
);
optimizer
.
solve
(
"bom"
);
// for (int scale : BenchmarkDataBuilder.SUPPORTED_SCALES) {
// MpPispipResultPersistenceService service =
// ctx.getBean(MpPispipResultPersistenceService.class);
//
// int scale = 10000;
// BenchmarkDataBuilder data = BenchmarkDataBuilder.forScale(scale);
// writeLog("===== TEST RUNNER START " + scale + "=====");
//
//
// int scale=10000;
// BenchmarkDataBuilder data =
// BenchmarkDataBuilder.forScale(scale);
// writeLog("===== TEST RUNNER START "+scale+"=====");
////
// data.init();
// data.init();
// writeLog("Data loaded: " + data.getProducts().size() + " products, "
// writeLog("Data loaded: " + data.getProducts().size() + " products, "
// + data.getOperations().size() + " operations");
// + data.getOperations().size() + " operations");
//
// long startTime = System.currentTimeMillis();
// MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
// MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
// optimizer.buildModel();
// optimizer.buildModel();
// optimizer.solve(String.valueOf(scale));
// optimizer.solve(String.valueOf(scale));
//
// // 构建结果并调用 save 入库
// ResultWriter writer = new ResultWriter(optimizer.getModel(), optimizer.getData(), startTime);
// OptimizationResult result = writer.buildResult();
// int saved = service.save(String.valueOf(scale), result);
// writeLog("PISPIP result saved: " + saved);
//
// writeLog("===== TEST RUNNER END =====");
// writeLog("===== TEST RUNNER END =====");
// }
}
catch
(
Exception
e
)
{
}
finally
{
SpringApplication
.
exit
(
ctx
);
}
}
}
}
}
\ No newline at end of file
src/main/java/com/aps/macroplanner/output/ResultWriter.java
View file @
f11971ca
This diff is collapsed.
Click to expand it.
src/main/java/com/aps/macroplanner/output/dto/BomStructureData.java
0 → 100644
View file @
f11971ca
package
com
.
aps
.
macroplanner
.
output
.
dto
;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* BOM 结构持久化数据 — 求解后将静态工艺/BOM 关系固化,供查询接口按需组装产品生产网络。
*
* <p>与求解结果 (period_tasks / pispips / salesdemand / operation_demands) 分离:
* 求解结果存 "数值", 本结构存 "关系" (谁产出什么、谁消耗什么、消耗因子)。</p>
*
* <p>字段全部使用扁平化嵌套 POJO + public 字段, 便于 Jackson 序列化/反序列化。</p>
*/
public
class
BomStructureData
{
public
List
<
ProductInfo
>
products
=
new
ArrayList
<>();
public
List
<
StockingPointInfo
>
stockingPoints
=
new
ArrayList
<>();
public
List
<
ProductSpMappingInfo
>
productSpMappings
=
new
ArrayList
<>();
public
List
<
OperationInfo
>
operations
=
new
ArrayList
<>();
public
List
<
OperationInputInfo
>
operationInputs
=
new
ArrayList
<>();
public
List
<
PeriodInfo
>
periods
=
new
ArrayList
<>();
public
List
<
InitialInventoryInfo
>
initialInventories
=
new
ArrayList
<>();
public
List
<
InTransitSupplyInfo
>
inTransitSupplies
=
new
ArrayList
<>();
// ==================== 扁平 POJO ====================
public
static
class
ProductInfo
{
public
String
id
;
public
String
name
;
public
String
code
;
}
public
static
class
StockingPointInfo
{
public
String
id
;
public
String
name
;
}
public
static
class
ProductSpMappingInfo
{
public
String
productId
;
public
String
spId
;
}
public
static
class
OperationInfo
{
public
String
id
;
public
String
name
;
public
int
leadTimeDays
;
public
boolean
hasLotSize
;
public
Double
lotSize
;
public
List
<
OperationOutputInfo
>
outputs
=
new
ArrayList
<>();
public
List
<
UnitOperationInfo
>
unitOperations
=
new
ArrayList
<>();
}
public
static
class
OperationOutputInfo
{
public
String
productId
;
public
String
spId
;
}
public
static
class
UnitOperationInfo
{
public
String
unitId
;
public
String
unitName
;
public
double
capacityCoeff
;
}
public
static
class
OperationInputInfo
{
public
String
operationId
;
public
String
inputProductId
;
public
String
inputSpId
;
public
String
inputSpName
;
public
double
factor
;
}
public
static
class
PeriodInfo
{
public
int
index
;
public
String
name
;
public
String
startDate
;
}
public
static
class
InitialInventoryInfo
{
public
String
productId
;
public
String
spId
;
public
double
quantity
;
}
public
static
class
InTransitSupplyInfo
{
public
String
productId
;
public
String
spId
;
public
String
arrivalDate
;
public
double
quantity
;
}
}
src/main/java/com/aps/macroplanner/output/dto/OperationDemandResult.java
0 → 100644
View file @
f11971ca
package
com
.
aps
.
macroplanner
.
output
.
dto
;
/**
* 工序消耗物料结果 — 每个工序在每周期消耗每种 BOM 输入物料的量 (OperationDemandQty)。
*
* <p>用于按需组装产品生产网络时还原 "消费者 consumedQty" 与 "BOM 子物料 consumedQty"。
* 扁平 POJO, 直接映射到 operation_demands.parquet。</p>
*/
public
class
OperationDemandResult
{
private
String
operationId
;
private
String
inputProductId
;
private
String
inputSpId
;
private
int
periodIndex
;
private
double
quantity
;
public
OperationDemandResult
()
{
}
public
OperationDemandResult
(
String
operationId
,
String
inputProductId
,
String
inputSpId
,
int
periodIndex
,
double
quantity
)
{
this
.
operationId
=
operationId
;
this
.
inputProductId
=
inputProductId
;
this
.
inputSpId
=
inputSpId
;
this
.
periodIndex
=
periodIndex
;
this
.
quantity
=
quantity
;
}
public
String
getOperationId
()
{
return
operationId
;
}
public
void
setOperationId
(
String
v
)
{
this
.
operationId
=
v
;
}
public
String
getInputProductId
()
{
return
inputProductId
;
}
public
void
setInputProductId
(
String
v
)
{
this
.
inputProductId
=
v
;
}
public
String
getInputSpId
()
{
return
inputSpId
;
}
public
void
setInputSpId
(
String
v
)
{
this
.
inputSpId
=
v
;
}
public
int
getPeriodIndex
()
{
return
periodIndex
;
}
public
void
setPeriodIndex
(
int
v
)
{
this
.
periodIndex
=
v
;
}
public
double
getQuantity
()
{
return
quantity
;
}
public
void
setQuantity
(
double
v
)
{
this
.
quantity
=
v
;
}
}
src/main/java/com/aps/service/MacroPlannerProductNetworkService.java
0 → 100644
View file @
f11971ca
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment