Skip to content

Commit

Permalink
Merge pull request #87 from nextmv-io/feature/python-nextmv-model
Browse files Browse the repository at this point in the history
Use nextmv.Model syntax
  • Loading branch information
sebastian-quintero authored Dec 10, 2024
2 parents 1a38fa0 + 34693a7 commit 7f62ad2
Show file tree
Hide file tree
Showing 43 changed files with 2,010 additions and 1,958 deletions.
2 changes: 1 addition & 1 deletion .nextmv/release/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ boto3>=1.34.33
pyyaml>=6.0.1
ruff>=0.1.7
requests>=2.26.0
nextmv==0.13.1
nextmv==0.14.1
116 changes: 59 additions & 57 deletions python-ampl-facilitylocation/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,66 +49,68 @@ def main() -> None:
nextmv.log(f" - facilities: {input.data.get('FACILITIES', [])}")
nextmv.log(f" - customers: {input.data.get('CUSTOMERS', 0)}")

output = solve(input, options)
model = DecisionModel()
output = model.solve(input)
nextmv.write_local(output, path=options.output)


def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
"""Solves the given problem and returns the solution."""

start_time = time.time()
nextmv.redirect_stdout() # Solver chatter is logged to stderr.

# Activate license.
license_used = activate_license()
options.license_used = license_used

# Defines the model.
ampl = AMPL()
ampl.read(f"{options.modelpath}/floc_bend.mod")
ampl.set["FACILITIES"] = input.data["FACILITIES"]
ampl.set["CUSTOMERS"] = input.data["CUSTOMERS"]
ampl.set["SCENARIOS"] = input.data["SCENARIOS"]
ampl.param["prob"] = input.data["prob"]
ampl.param["fixed_cost"] = pd.read_json(io.StringIO(input.data["fixed_cost"]), orient="table")
ampl.param["facility_capacity"] = pd.read_json(io.StringIO(input.data["facility_capacity"]), orient="table")
ampl.param["variable_cost"] = pd.read_json(io.StringIO(input.data["variable_cost"]), orient="table")
ampl.param["customer_demand"] = pd.read_json(io.StringIO(input.data["customer_demand"]), orient="table")

# Sets the solver and options.
provider = options.provider
ampl.option["solver"] = provider
if provider in SUPPORTED_PROVIDER_DURATIONS.keys():
opt_name = f"{provider}_options"
if not ampl.option[opt_name]:
ampl.option[opt_name] = ""
ampl.option[opt_name] += f" {SUPPORTED_PROVIDER_DURATIONS[provider]}={options.duration}"
solve_output = ampl.get_output(f"include {options.runpath}/floc_bend.run;")

statistics = nextmv.Statistics(
run=nextmv.RunStatistics(duration=time.time() - start_time),
result=nextmv.ResultStatistics(
duration=ampl.get_value("_total_solve_time"),
value=round(ampl.get_value("operating_cost"), 6),
custom={
"status": ampl.solve_result,
"variables": ampl.get_value("_nvars"),
"constraints": ampl.get_value("_ncons"),
},
),
)

solution = {
"facility_open": ampl.get_data("facility_open").to_pandas().to_json(orient="table"),
"total_cost": ampl.get_value("total_cost"),
"solve_output": solve_output,
}

return nextmv.Output(
options=options,
solution=solution,
statistics=statistics,
)
class DecisionModel(nextmv.Model):
def solve(self, input: nextmv.Input) -> nextmv.Output:
"""Solves the given problem and returns the solution."""

start_time = time.time()
nextmv.redirect_stdout() # Solver chatter is logged to stderr.

# Activate license.
license_used = activate_license()
input.options.license_used = license_used

# Defines the model.
ampl = AMPL()
ampl.read(f"{input.options.modelpath}/floc_bend.mod")
ampl.set["FACILITIES"] = input.data["FACILITIES"]
ampl.set["CUSTOMERS"] = input.data["CUSTOMERS"]
ampl.set["SCENARIOS"] = input.data["SCENARIOS"]
ampl.param["prob"] = input.data["prob"]
ampl.param["fixed_cost"] = pd.read_json(io.StringIO(input.data["fixed_cost"]), orient="table")
ampl.param["facility_capacity"] = pd.read_json(io.StringIO(input.data["facility_capacity"]), orient="table")
ampl.param["variable_cost"] = pd.read_json(io.StringIO(input.data["variable_cost"]), orient="table")
ampl.param["customer_demand"] = pd.read_json(io.StringIO(input.data["customer_demand"]), orient="table")

# Sets the solver and options.
provider = input.options.provider
ampl.option["solver"] = provider
if provider in SUPPORTED_PROVIDER_DURATIONS.keys():
opt_name = f"{provider}_options"
if not ampl.option[opt_name]:
ampl.option[opt_name] = ""
ampl.option[opt_name] += f" {SUPPORTED_PROVIDER_DURATIONS[provider]}={input.options.duration}"
solve_output = ampl.get_output(f"include {input.options.runpath}/floc_bend.run;")

statistics = nextmv.Statistics(
run=nextmv.RunStatistics(duration=time.time() - start_time),
result=nextmv.ResultStatistics(
duration=ampl.get_value("_total_solve_time"),
value=round(ampl.get_value("operating_cost"), 6),
custom={
"status": ampl.solve_result,
"variables": ampl.get_value("_nvars"),
"constraints": ampl.get_value("_ncons"),
},
),
)

solution = {
"facility_open": ampl.get_data("facility_open").to_pandas().to_json(orient="table"),
"total_cost": ampl.get_value("total_cost"),
"solve_output": solve_output,
}

return nextmv.Output(
options=input.options,
solution=solution,
statistics=statistics,
)


def activate_license() -> str:
Expand Down
2 changes: 1 addition & 1 deletion python-ampl-facilitylocation/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ ampl-module-open==20240121
ampl-module-scip==20240121
ampl-module-xpress==20240115

nextmv==0.13.1
nextmv==0.14.1
pandas==2.2.2
168 changes: 85 additions & 83 deletions python-ampl-knapsack/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,92 +45,94 @@ def main() -> None:
nextmv.log(f" - items: {len(input.data.get('items', []))}")
nextmv.log(f" - capacity: {input.data.get('weight_capacity', 0)}")

output = solve(input, options)
model = DecisionModel()
output = model.solve(input)
nextmv.write_local(output, path=options.output)


def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
"""Solves the given problem and returns the solution."""

start_time = time.time()
nextmv.redirect_stdout() # Solver chatter is logged to stderr.

# Activate license.
license_used = activate_license()
options.license_used = license_used

# Defines the model.
ampl = AMPL()
ampl.eval(
r"""
# Sets
set I; # Set of items.
# Parameters
param W >= 0; # Maximum weight capacity.
param v {I} >= 0; # Value of each item.
param w {I} >= 0; # Weight of each item.
# Variables
var x {I} binary; # 1 if item is selected, 0 otherwise.
# Objective
maximize z: sum {i in I} v[i] * x[i];
# Constraints
s.t. weight_limit: sum {i in I} w[i] * x[i] <= W;
"""
)

# Sets the solver and options.
provider = options.provider
ampl.option["solver"] = provider
if provider in SUPPORTED_PROVIDER_DURATIONS.keys():
ampl.option[f"{provider}_options"] = f"{SUPPORTED_PROVIDER_DURATIONS[provider]}={options.duration}"

# Set the data on the model.
ampl.set["I"] = [item["id"] for item in input.data["items"]]
ampl.param["W"] = input.data["weight_capacity"]
ampl.param["v"] = {item["id"]: item["value"] for item in input.data["items"]}
ampl.param["w"] = {item["id"]: item["weight"] for item in input.data["items"]}

# Solves the problem. Verbose mode is turned off to avoid printing to
# stdout. Only the output should be printed to stdout.
ampl.solve()

# Convert to solution format.
value = ampl.get_objective("z")
chosen_items = []
if value:
chosen_items = [item for item in input.data["items"] if ampl.get_variable("x")[item["id"]].value() > 0.9]

solve_result = ampl.solve_result_num
status = "unknown"
for s in STATUS:
lb = s.get("lb")
ub = s.get("ub")
if lb is not None and ub is not None and lb <= solve_result <= ub:
status = s.get("status")
break

statistics = nextmv.Statistics(
run=nextmv.RunStatistics(duration=time.time() - start_time),
result=nextmv.ResultStatistics(
duration=ampl.get_value("_total_solve_time"),
value=value.value(),
custom={
"status": status,
"variables": ampl.get_value("_nvars"),
"constraints": ampl.get_value("_ncons"),
},
),
)

return nextmv.Output(
options=options,
solution={"items": chosen_items},
statistics=statistics,
)
class DecisionModel(nextmv.Model):
def solve(self, input: nextmv.Input) -> nextmv.Output:
"""Solves the given problem and returns the solution."""

start_time = time.time()
nextmv.redirect_stdout() # Solver chatter is logged to stderr.

# Activate license.
license_used = activate_license()
input.options.license_used = license_used

# Defines the model.
ampl = AMPL()
ampl.eval(
r"""
# Sets
set I; # Set of items.
# Parameters
param W >= 0; # Maximum weight capacity.
param v {I} >= 0; # Value of each item.
param w {I} >= 0; # Weight of each item.
# Variables
var x {I} binary; # 1 if item is selected, 0 otherwise.
# Objective
maximize z: sum {i in I} v[i] * x[i];
# Constraints
s.t. weight_limit: sum {i in I} w[i] * x[i] <= W;
"""
)

# Sets the solver and options.
provider = input.options.provider
ampl.option["solver"] = provider
if provider in SUPPORTED_PROVIDER_DURATIONS.keys():
ampl.option[f"{provider}_options"] = f"{SUPPORTED_PROVIDER_DURATIONS[provider]}={input.options.duration}"

# Set the data on the model.
ampl.set["I"] = [item["id"] for item in input.data["items"]]
ampl.param["W"] = input.data["weight_capacity"]
ampl.param["v"] = {item["id"]: item["value"] for item in input.data["items"]}
ampl.param["w"] = {item["id"]: item["weight"] for item in input.data["items"]}

# Solves the problem. Verbose mode is turned off to avoid printing to
# stdout. Only the output should be printed to stdout.
ampl.solve()

# Convert to solution format.
value = ampl.get_objective("z")
chosen_items = []
if value:
chosen_items = [item for item in input.data["items"] if ampl.get_variable("x")[item["id"]].value() > 0.9]

solve_result = ampl.solve_result_num
status = "unknown"
for s in STATUS:
lb = s.get("lb")
ub = s.get("ub")
if lb is not None and ub is not None and lb <= solve_result <= ub:
status = s.get("status")
break

statistics = nextmv.Statistics(
run=nextmv.RunStatistics(duration=time.time() - start_time),
result=nextmv.ResultStatistics(
duration=ampl.get_value("_total_solve_time"),
value=value.value(),
custom={
"status": status,
"variables": ampl.get_value("_nvars"),
"constraints": ampl.get_value("_ncons"),
},
),
)

return nextmv.Output(
options=input.options,
solution={"items": chosen_items},
statistics=statistics,
)


def activate_license() -> str:
Expand Down
2 changes: 1 addition & 1 deletion python-ampl-knapsack/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ ampl-module-open==20240121
ampl-module-scip==20240121
ampl-module-xpress==20240115

nextmv==0.13.1
nextmv==0.14.1
Loading

0 comments on commit 7f62ad2

Please sign in to comment.