# Use the existing connected PyFluent variable solver.
import math

task_directory = r"C:\Users\13977\AppData\Local\FluentAGI\data\runs\20261018-192740-fe6b6c"
s = solver.settings
baseline_cells = 271220
target_cells = 2 * baseline_cells
runtime_cell_limit = 4000000
if not s.setup.is_active() or not s.setup.models.is_active():
    raise RuntimeError("Expected the existing initialized car case")
s.file.write_case_data(file_name=task_directory+r"\before-refinement-ad9ac9.cas.h5")
bc = s.setup.boundary_conditions
rd = s.solution.report_definitions
run = s.solution.run_calculation
print({"time_mode": s.setup.general.solver.time.get_state(),
       "calculation_state": run.get_state(),
       "target_cells": target_cells, "runtime_cell_limit": runtime_cell_limit})
print({"inlet_before": bc.velocity_inlet["inlet"].get_state(),
       "walls": bc.wall.get_object_names()})

def choose(node, candidates):
    norm = lambda x: str(x).lower().replace(" ", "").replace("-", "").replace("_", "")
    allowed = node.allowed_values()
    for candidate in candidates:
        for value in allowed:
            if norm(value) == norm(candidate):
                node.set_state(value)
                return value
    raise RuntimeError(str({"requested": candidates, "allowed": allowed}))

def select_type(node, predicate):
    allowed = node.allowed_values()
    for value in allowed:
        if predicate(str(value).lower()):
            node.set_state(value)
            return value
    raise RuntimeError(str({"unsupported_report_or_method": allowed}))

def constant(node, value):
    state = node.get_state()
    if isinstance(state, dict) and "value" in node.child_names:
        if "option" in node.child_names:
            choose(node.option, ["value", "constant"])
        node.value = value
    else:
        node.set_state(value)

def vector(node, values):
    state = node.get_state()
    if isinstance(state, list) and state and isinstance(state[0], dict):
        for i, value in enumerate(values):
            constant(node[i], value)
    else:
        node.set_state(values)

def report_number(name):
    result = rd.compute(report_defs=[name])
    blocks = result if isinstance(result, list) else [result]
    for block in blocks:
        if isinstance(block, dict) and name in block:
            value = block[name]
            if isinstance(value, (list, tuple)):
                value = value[0]
            return float(value)
    raise RuntimeError(str({"unrecognized_report": name, "result": result}))

cell_report = "cell_count_ad9ac9"
if cell_report not in rd.mesh.get_object_names():
    rd.mesh.create(name=cell_report)
select_type(rd.mesh[cell_report].report_type,
            lambda x: "cell" in x and "count" in x)
fluid_zones = s.setup.cell_zone_conditions.fluid.get_object_names()
rd.mesh[cell_report].cell_zones = fluid_zones
clock_report = "flow_time_ad9ac9"
if clock_report not in rd.time.get_object_names():
    rd.time.create(name=clock_report)
initial_time = report_number(clock_report)
initial_cells = int(round(report_number(cell_report)))
print({"actual_initial_time": initial_time, "actual_initial_cells": initial_cells})
if initial_cells > int(target_cells * 1.15):
    raise RuntimeError("Existing mesh already exceeds this refinement target")

required_walls = [
    "wall-body", "wall-frontwing", "wall-rearwing", "wall-ground",
    "wall-wheel-fl", "wall-wheel-fr", "wall-wheel-rl", "wall-wheel-rr"]
actual_walls = bc.wall.get_object_names()
if any(name not in actual_walls for name in required_walls):
    raise RuntimeError(str({"missing_car_boundaries": actual_walls}))
adapt = s.mesh.adapt
controls = adapt.set
print({"adaptation_controls_before": controls.get_state()})
if "maximum_cell_count" not in controls.child_names:
    raise RuntimeError("Native adaption lacks the required cell-count control")
select_type(controls.adaption_method,
            lambda x: ("puma" in x or "poly" in x) and "2.5" not in x)
controls.maximum_refinement_level = 1
controls.minimum_edge_length = 0.002
controls.maximum_cell_count = target_cells
registers = s.solution.cell_registers
regions = [
    ("frontwing", [-0.04,-0.85,0.035], [0.36,0.85,0.29], 335000),
    ("rearwing", [2.81,-0.56,0.84], [3.25,0.56,1.10], 395000),
    ("diffuser", [2.12,-0.59,0.045], [3.28,0.59,0.33], 450000),
    ("sidepods", [0.98,-0.59,0.14], [2.80,0.59,0.70], 490000),
    ("wake", [3.12,-1.20,0.03], [7.50,1.20,1.45], target_cells),
    ("car_nearfield", [-0.08,-1.0,0.01], [3.30,1.0,1.14], target_cells)]
for label, low, high, budget in regions:
    current = int(round(report_number(cell_report)))
    if current >= int(0.98 * target_cells):
        break
    if current >= budget:
        continue
    controls.maximum_cell_count = min(budget, target_cells, runtime_cell_limit)
    name = "ref_" + label + "_ad9ac9"
    if name not in registers.get_object_names():
        registers.create(name=name)
    register = registers[name]
    choose(register.type.option, ["hexahedron"])
    region = register.type.hexahedron
    region.min_point = low
    region.max_point = high
    region.inside = True
    print({"refinement_region": label, "register": register.get_state(),
           "cell_budget": controls.maximum_cell_count.get_state()})
    if "refine" not in register.command_names:
        raise RuntimeError("Native cell-register refinement unavailable")
    register.refine()
    current = int(round(report_number(cell_report)))
    print({"region_finished": label, "actual_cells": current})
    if current > min(runtime_cell_limit, int(target_cells * 1.15)):
        s.file.write_case_data(file_name=task_directory+r"\refinement-review-ad9ac9.cas.h5")
        raise RuntimeError("Refinement exceeded the intended cell budget")
controls.maximum_cell_count = target_cells
s.mesh.check()
final_cells = int(round(report_number(cell_report)))
print({"actual_refined_cells": final_cells,
       "ratio_to_original": final_cells / baseline_cells,
       "solution_time_after_refinement": report_number(clock_report)})
if final_cells <= initial_cells and initial_cells < int(target_cells * 0.9):
    raise RuntimeError("Requested refinement did not increase the cell count")
s.file.write_case_data(file_name=task_directory+r"\car-refined-ad9ac9.cas.h5")

time_mode = str(s.setup.general.solver.time.get_state()).lower()
if "unsteady" not in time_mode:
    raise RuntimeError("Expected the existing transient solution; time mode preserved")
run.transient_controls.time_step_size = 0.001
ground = bc.wall["wall-ground"].momentum
print({"ground_before": ground.get_state()})
wheels = ["wall-wheel-" + side for side in ["fl", "fr", "rl", "rr"]]
for name in wheels:
    print({"wheel": name, "motion_before": bc.wall[name].momentum.get_state()})

def set_speed(value):
    constant(bc.velocity_inlet["inlet"].momentum.velocity, value)
    vector(ground.velocity_components, [value, 0.0, 0.0])
    for name in wheels:
        constant(bc.wall[name].momentum.rotation_speed, -value / 0.24)
    print({"requested_speed": value,
           "inlet_readback": bc.velocity_inlet["inlet"].momentum.get_state(),
           "ground_readback": ground.get_state()})

car_walls = [name for name in actual_walls if name != "wall-ground"]
force_reports = []
for name, zones, direction in [
    ("drag_80_ad9ac9", car_walls, [1,0,0]),
    ("downforce_80_ad9ac9", car_walls, [0,0,-1]),
    ("front_partition_80_ad9ac9", ["wall-frontwing"], [0,0,-1]),
    ("rear_partition_80_ad9ac9", ["wall-rearwing"], [0,0,-1])]:
    if name not in rd.force.get_object_names():
        rd.force.create(name=name)
    rd.force[name].set_state({"zones": zones, "force_vector": direction})
    force_reports.append(name)
mass_reports = []
for label, boundaries in [("inlet", ["inlet"]), ("outlet", ["outlet"])]:
    name = "mass_" + label + "_ad9ac9"
    if name not in rd.flux.get_object_names():
        rd.flux.create(name=name)
    select_type(rd.flux[name].report_type, lambda x: "mass" in x and "flow" in x)
    rd.flux[name].boundaries = boundaries
    mass_reports.append(name)
monitor_name = "car_80_ad9ac9"
all_reports = force_reports + mass_reports + [clock_report]
for collection in [s.solution.monitor.report_files, s.solution.monitor.report_plots]:
    if monitor_name not in collection.get_object_names():
        collection.create(name=monitor_name)
    collection[monitor_name].report_defs = all_reports
    collection[monitor_name].active = True
s.solution.monitor.report_files[monitor_name].file_name = task_directory+r"\car-80-history-ad9ac9.out"

def evidence(label):
    print({"stage": label, "actual_time": report_number(clock_report),
           "forces": rd.compute(report_defs=force_reports)})
    mass_in, mass_out = [report_number(name) for name in mass_reports]
    print({"mass_in": mass_in, "mass_out": mass_out,
           "relative_mass_imbalance": abs(mass_in + mass_out) / max(abs(mass_in), 1e-12)})
    try:
        index, history = solver.monitors.get_monitor_set_data("residual")
        last = {key: float(value[-1]) for key, value in history.items() if len(value)}
        print({"residuals": last})
        if any(not math.isfinite(value) or value > 1e4 for value in last.values()):
            s.file.write_case_data(file_name=task_directory+r"\needs-review-80-ad9ac9.cas.h5")
            raise RuntimeError("Residual divergence detected")
    except RuntimeError:
        raise
    except Exception as error:
        print({"residual_read_error": str(error)})

for value in [40.0, 60.0, 80.0]:
    set_speed(value)
    run.dual_time_iterate(time_step_count=3, max_iter_per_step=60)
    evidence("speed_" + str(value))
for batch in range(3):
    run.dual_time_iterate(time_step_count=10, max_iter_per_step=60)
    evidence("80_settling_" + str(batch + 1))
s.file.write_case_data(file_name=task_directory+r"\car-80-before-animation-ad9ac9.cas.h5")

g = s.results.graphics
try:
    name = "refined_car_ad9ac9"
    if name not in g.mesh.get_object_names():
        g.mesh.create(name=name)
    g.mesh[name].surfaces_list = car_walls
    g.mesh[name].display()
    g.picture.save_picture(file_name=task_directory+r"\refined-car-ad9ac9.png")
except Exception as error:
    print({"mesh_graphics_error": str(error)})
section = fluentagi.create_section(axis="y", position=0.0)
print(section)
result = fluentagi.export_results(
    directory=task_directory, fields=["pressure", "velocity-magnitude"],
    surfaces=[section["surface"]])
print(result)
for name, kind, state in [
    ("car_pressure_80_ad9ac9", "contour",
     {"field":"pressure", "surfaces_list":car_walls, "filled":True}),
    ("car_streamlines_80_ad9ac9", "pathline",
     {"field":"velocity-magnitude", "release_from_surfaces":["inlet"], "step":3000, "skip":20})]:
    try:
        collection = getattr(g, kind)
        if name not in collection.get_object_names():
            collection.create(name=name)
        collection[name].set_state(state)
        collection[name].display()
        g.picture.save_picture(file_name=task_directory+"\\"+name+".png")
    except Exception as error:
        print({"graphics": name, "error": str(error)})
print(fluentagi.record_animation(
    directory=task_directory, field="velocity-magnitude", frames=24,
    steps_per_frame=2, max_iter_per_step=60, surfaces=[section["surface"]]))
evidence("final_80")
s.file.write_case_data(file_name=task_directory+r"\car-80-final-ad9ac9.cas.h5")
for name, obj in [("forces", s.solution.monitor.report_plots[monitor_name]),
                  ("residuals", s.solution.monitor.residual)]:
    try:
        obj.plot()
        g.picture.save_picture(file_name=task_directory+"\\"+name+"-80-ad9ac9.png")
    except Exception as error:
        print({"plot": name, "error": str(error)})
print({"initial_time": initial_time, "actual_final_time": report_number(clock_report),
       "actual_cells": int(round(report_number(cell_report))),
       "target_speed": 80.0, "convergence_verified": False})