{
  "schema_version": "1",
  "id": "pythonrobotics-dijkstra-planner",
  "name": "Dijkstra collision-checked grid path",
  "summary": "Dijkstra collision-checked grid path. Parameterized bounded JSON adapter; numerical operation, no hardware execution.",
  "capabilities": [
    "algorithm",
    "grid",
    "executable-json",
    "source-adaptation"
  ],
  "aliases": [
    "grid",
    "Dijkstra collision-checked grid path"
  ],
  "source": {
    "repository": "https://github.com/AtsushiSakai/PythonRobotics",
    "commit": "cdd0cc888802b584c2d654ca85e3c5460973487d",
    "paths": [
      "ArmNavigation/n_joint_arm_to_point_control/NLinkArm.py",
      "ArmNavigation/n_joint_arm_to_point_control/n_joint_arm_to_point_control.py",
      "InvertedPendulum/inverted_pendulum_lqr_control.py",
      "LICENSE",
      "Localization/extended_kalman_filter/ekf_with_velocity_correction.py",
      "Localization/extended_kalman_filter/extended_kalman_filter.py",
      "Localization/unscented_kalman_filter/unscented_kalman_filter.py",
      "PathPlanning/AStar/a_star.py",
      "PathPlanning/BSplinePath/bspline_path.py",
      "PathPlanning/BezierPath/bezier_path.py",
      "PathPlanning/BidirectionalAStar/bidirectional_a_star.py",
      "PathPlanning/BidirectionalBreadthFirstSearch/bidirectional_breadth_first_search.py",
      "PathPlanning/BreadthFirstSearch/breadth_first_search.py",
      "PathPlanning/Catmull_RomSplinePath/catmull_rom_spline_path.py",
      "PathPlanning/CubicSpline/cubic_spline_planner.py",
      "PathPlanning/DepthFirstSearch/depth_first_search.py",
      "PathPlanning/Dijkstra/dijkstra.py",
      "PathPlanning/DubinsPath/dubins_path_planner.py",
      "PathPlanning/DynamicWindowApproach/dynamic_window_approach.py",
      "PathPlanning/GreedyBestFirstSearch/greedy_best_first_search.py",
      "PathPlanning/PotentialFieldPlanning/potential_field_planning.py",
      "PathPlanning/ProbabilisticRoadMap/probabilistic_road_map.py",
      "PathPlanning/QuinticPolynomialsPlanner/quintic_polynomials_planner.py",
      "PathPlanning/RRT/rrt.py",
      "PathPlanning/RRT/rrt_with_pathsmoothing.py",
      "PathPlanning/RRTStar/rrt_star.py",
      "PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py",
      "PathTracking/pure_pursuit/pure_pursuit.py",
      "PathTracking/stanley_control/stanley_control.py",
      "utils/__init__.py",
      "utils/angle.py",
      "utils/plot.py",
      "ArmNavigation/n_joint_arm_to_point_control/__init__.py",
      "ArmNavigation/__init__.py",
      "__init__.py",
      "Localization/__init__.py",
      "PathPlanning/__init__.py",
      "PathPlanning/CubicSpline/__init__.py",
      "PathPlanning/DubinsPath/__init__.py",
      "PathPlanning/RRT/__init__.py",
      "PathPlanning/RRTStar/__init__.py",
      "PathTracking/__init__.py"
    ],
    "archive_url": "https://codeload.github.com/AtsushiSakai/PythonRobotics/tar.gz/cdd0cc888802b584c2d654ca85e3c5460973487d",
    "archive_sha256": "6d3ccf43be4ae770a9e8dffd463c2561665847df5b7ddf693b01130004dbcee5",
    "archive_bytes": 12311789,
    "authors": [
      "Atsushi Sakai and PythonRobotics contributors"
    ]
  },
  "license": {
    "spdx": "MIT",
    "url": "https://github.com/AtsushiSakai/PythonRobotics/blob/cdd0cc888802b584c2d654ca85e3c5460973487d/LICENSE",
    "verified": true,
    "scope": "Selected PythonRobotics algorithm demonstration source under the pinned MIT license text, Copyright (c) 2016 - now Atsushi Sakai and other contributors. Preserve repository license and each selected file's author notices. This is source-text scope only; dependencies, mathematical references, generated plots and any external assets retain separate terms. GitHub's automated classifier reports NOASSERTION; the MIT identity here comes from the exact reviewed LICENSE bytes, not that classifier."
  },
  "compatibility": [
    {
      "runtime": "pythonrobotics",
      "runtime_version": "cdd0cc888802",
      "robot": "mathematical-model",
      "controller": "json-contract-v1",
      "observation": "explicit-json-input",
      "os": "windows",
      "architecture": "x86_64",
      "python": "3.10",
      "status": "hub_verified"
    },
    {
      "runtime": "pythonrobotics",
      "runtime_version": "cdd0cc888802",
      "robot": "mathematical-model",
      "controller": "json-contract-v1",
      "observation": "explicit-json-input",
      "os": "linux",
      "architecture": "x86_64",
      "python": "3.10",
      "status": "hub_verified"
    }
  ],
  "observation": {
    "required": [
      "explicit_json_task_input"
    ],
    "privileged_state": false,
    "description": "All inputs and outputs follow interface v1. Simulation adapters query exact synthetic simulator state; no perception or physical sensor compatibility."
  },
  "requirements": {
    "python": ">=3.10,<3.11",
    "packages": [
      "contourpy==1.3.2",
      "cycler==0.12.1",
      "fonttools==4.65.0",
      "kiwisolver==1.5.1",
      "matplotlib==3.9.4",
      "numpy==1.26.4",
      "packaging==26.3",
      "pillow==12.3.0",
      "pyparsing==3.3.3",
      "python-dateutil==2.9.0.post0",
      "scipy==1.13.1",
      "six==1.17.0"
    ],
    "hardware": [
      "x86_64 CPU; no GPU or robot required"
    ],
    "rendering": "No display. Matplotlib Agg for numerical imports; PyBullet DIRECT for simulation.",
    "api_keys": [],
    "network": [
      "Pinned source archive once during preparation",
      "PyPI for declared dependency installation"
    ],
    "dependency_lock": "Runtime dependency versions are pinned for the supported Python 3.10 x86_64 CPU environments and checked against execution receipts. Platform-specific wheel hashes are not represented as a universal artifact lock; prepare captures pip freeze."
  },
  "invocation": {
    "entrypoint": "python run.py",
    "argv": [
      "{python}",
      "{recipe_dir}/run.py",
      "--source-dir",
      "{source_dir}",
      "--example"
    ],
    "inputs": {
      "start": [
        2,
        2
      ],
      "goal": [
        18,
        18
      ],
      "bounds": [
        0,
        20
      ],
      "obstacles": [
        [
          10,
          1
        ],
        [
          10,
          2
        ],
        [
          10,
          3
        ],
        [
          10,
          4
        ],
        [
          10,
          5
        ],
        [
          10,
          6
        ],
        [
          10,
          7
        ],
        [
          10,
          8
        ],
        [
          10,
          9
        ],
        [
          10,
          10
        ],
        [
          10,
          11
        ],
        [
          10,
          12
        ]
      ],
      "robot_radius": 0.3
    },
    "outputs": {
      "format": "single JSON object on stdout",
      "payload_selector": "outputs",
      "schema": {
        "type": "object",
        "required": [
          "path_xy",
          "length_m"
        ],
        "additionalProperties": false,
        "properties": {
          "path_xy": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2048,
            "items": {
              "type": "array",
              "minItems": 2,
              "maxItems": 2,
              "items": {
                "type": "number",
                "minimum": -100.0,
                "maximum": 100.0
              }
            },
            "x-unit": "m",
            "x-frame": "planar_map_xy",
            "x-order": [
              "x",
              "y"
            ],
            "description": "path_xy; units=m; frame=planar_map_xy; component order=x,y"
          },
          "length_m": {
            "type": "number",
            "minimum": 0,
            "maximum": 100000,
            "x-unit": "m",
            "x-frame": "planar_map_xy",
            "description": "length_m; units=m; frame=planar_map_xy"
          }
        }
      }
    },
    "example": "After prepare, replace --example in next_command with --input /absolute/path/task.json; use --describe for the exact contract without execution."
  },
  "interface": {
    "version": "1",
    "kind": "algorithm",
    "abstraction_level": 0,
    "execution_domain": "computation",
    "language": "python",
    "input_transport": "json_file_or_inline",
    "output_transport": "json_stdout",
    "conventions": {
      "linear_unit": "m",
      "angular_unit": "rad",
      "time_unit": "s",
      "coordinate_frame": "planar_map_xy",
      "joint_order": "not_used",
      "quaternion_order": "not_used"
    },
    "input_schema": {
      "type": "object",
      "required": [
        "start",
        "goal",
        "bounds",
        "obstacles",
        "robot_radius"
      ],
      "additionalProperties": false,
      "properties": {
        "start": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": {
            "type": "number",
            "minimum": 1,
            "maximum": 39
          },
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "x-order": [
            "x",
            "y"
          ],
          "description": "start; units=m; frame=planar_map_xy; component order=x,y"
        },
        "goal": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": {
            "type": "number",
            "minimum": 1,
            "maximum": 39
          },
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "x-order": [
            "x",
            "y"
          ],
          "description": "goal; units=m; frame=planar_map_xy; component order=x,y"
        },
        "bounds": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40
          },
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "description": "bounds; units=m; frame=planar_map_xy"
        },
        "obstacles": {
          "type": "array",
          "minItems": 0,
          "maxItems": 200,
          "items": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "type": "number",
              "minimum": 0,
              "maximum": 40
            }
          },
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "x-order": [
            "x",
            "y"
          ],
          "description": "obstacles; units=m; frame=planar_map_xy; component order=x,y"
        },
        "robot_radius": {
          "type": "number",
          "minimum": 0.1,
          "maximum": 0.6,
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "description": "robot_radius; units=m; frame=planar_map_xy"
        }
      }
    },
    "output_schema": {
      "type": "object",
      "required": [
        "path_xy",
        "length_m"
      ],
      "additionalProperties": false,
      "properties": {
        "path_xy": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2048,
          "items": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "type": "number",
              "minimum": -100.0,
              "maximum": 100.0
            }
          },
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "x-order": [
            "x",
            "y"
          ],
          "description": "path_xy; units=m; frame=planar_map_xy; component order=x,y"
        },
        "length_m": {
          "type": "number",
          "minimum": 0,
          "maximum": 100000,
          "x-unit": "m",
          "x-frame": "planar_map_xy",
          "description": "length_m; units=m; frame=planar_map_xy"
        }
      }
    },
    "preconditions": [
      "Inputs use the declared units and frame; no implicit transforms.",
      "This exact numerical/simulation model is not a physical robot configuration."
    ],
    "postconditions": [
      "All declared checks must pass and all outputs must be finite.",
      "Success applies only to this operation and model, not higher-level task completion."
    ],
    "failure_codes": [
      "invalid_input_or_environment",
      "execution_failed",
      "task_failed"
    ],
    "composition": {
      "depends_on": [],
      "automatic_frame_conversion": false,
      "automatic_hardware_execution": false
    },
    "output_selector": "outputs",
    "origin_reference_ids": [],
    "origin_relationship": "adapted_from; inspect declared model and inputs, not original robot compatibility"
  },
  "completion": {
    "success_condition": "Exit 0, status=task_complete, success=true, finite outputs satisfying interface.output_schema and all declared numerical/model checks. This is not a physical robot or general task-success claim.",
    "timeout_seconds": 30,
    "failure_modes": [
      "Invalid or oversized JSON",
      "Source checksum mismatch",
      "Unavailable pinned dependency",
      "No valid path or postcondition failure",
      "Execution exceeding the declared bounded algorithm budget"
    ],
    "stop_condition": "Caller must terminate the separate adapter process after 30 seconds; adapters bound iterations/scene steps and close simulation clients."
  },
  "preparation": {
    "recipe_path": "recipes/pythonrobotics-dijkstra-planner",
    "requirements_file": "recipes/pythonrobotics-dijkstra-planner/requirements.txt",
    "source_selection": "declared_paths",
    "install_argv": [
      "{python}",
      "-m",
      "pip",
      "install",
      "--only-binary=:all:",
      "-r",
      "{recipe_dir}/requirements.txt"
    ],
    "execution_separate": true,
    "files": {
      "run.py": "# Generated from tools/recipe_adapters and catalog/executable-conversions.json.\n# Hub adapter: MIT. Adapted upstream example retains its original license and authors.\n\"\"\"Source template for standalone, bounded JSON recipe adapters (MIT, Hub authors).\n\nThe generator embeds this template and a reviewed backend in each published\nrecipe. Importing this template does not import or execute upstream source.\n\"\"\"\nimport argparse\nimport contextlib\nimport hashlib\nimport importlib.util\nimport io\nimport json\nimport math\nimport os\nfrom pathlib import Path\nimport random\nimport sys\n\n\nclass ContractError(ValueError):\n    pass\n\n\ndef validate_data(value, schema, path='$'):\n    \"\"\"Supported, published JSON Schema subset; bool is never a number.\"\"\"\n    kind = schema.get('type')\n    checks = {'object': lambda: isinstance(value, dict), 'array': lambda: isinstance(value, list),\n              'number': lambda: type(value) in (int, float) and math.isfinite(value),\n              'integer': lambda: type(value) is int, 'string': lambda: isinstance(value, str),\n              'boolean': lambda: isinstance(value, bool), 'null': lambda: value is None}\n    if kind and (kind not in checks or not checks[kind]()):\n        raise ContractError(path + ': expected ' + str(kind))\n    if 'enum' in schema and value not in schema['enum']:\n        raise ContractError(path + ': unsupported value')\n    if type(value) in (int, float):\n        if not math.isfinite(value) or value < schema.get('minimum', value) or value > schema.get('maximum', value):\n            raise ContractError(path + ': outside numeric bounds')\n    if isinstance(value, dict):\n        if len(value) > 32: raise ContractError(path + ': too many keys')\n        for name in schema.get('required', []):\n            if name not in value: raise ContractError(path + '.' + name + ': required')\n        props = schema.get('properties', {})\n        for name, item in value.items():\n            if name not in props and schema.get('additionalProperties') is False:\n                raise ContractError(path + '.' + name + ': unknown field')\n            validate_data(item, props.get(name, {}), path + '.' + name)\n    elif isinstance(value, list):\n        if not schema.get('minItems', 0) <= len(value) <= schema.get('maxItems', 4096):\n            raise ContractError(path + ': invalid array length')\n        for i, item in enumerate(value): validate_data(item, schema.get('items', {}), path + '[' + str(i) + ']')\n    elif isinstance(value, str) and len(value) > schema.get('maxLength', 4096):\n        raise ContractError(path + ': string too long')\n\n\ndef verify_source(root, spec):\n    root = Path(root).resolve()\n    for name, expected in spec['source_hashes'].items():\n        target = root / name\n        if target.is_symlink() or not target.resolve().is_relative_to(root) or not target.is_file():\n            raise ContractError('Pinned source missing or outside source root: ' + name)\n        if hashlib.sha256(target.read_bytes()).hexdigest() != expected:\n            raise ContractError('Pinned source checksum changed: ' + name)\n    return root\n\n\ndef load_upstream(root, relative):\n    \"\"\"Only a generator-pinned path is accepted; never a path from task input.\"\"\"\n    target = root / relative\n    for directory in [root, root / 'PathPlanning', target.parent]:\n        sys.path.insert(0, str(directory))\n    module_spec = importlib.util.spec_from_file_location('rsh_upstream', target)\n    module = importlib.util.module_from_spec(module_spec)\n    module_spec.loader.exec_module(module)\n    if hasattr(module, 'show_animation'): module.show_animation = False\n    return module\n\n\ndef clean_json(value):\n    if hasattr(value, 'tolist'): return clean_json(value.tolist())\n    if isinstance(value, dict): return {k: clean_json(v) for k,v in value.items()}\n    if isinstance(value, (tuple, list)): return [clean_json(v) for v in value]\n    if hasattr(value, 'item'): return clean_json(value.item())\n    if isinstance(value, float) and not math.isfinite(value): raise ContractError('Non-finite output')\n    return value\n\n\ndef adapter_main(spec, backend):\n    parser = argparse.ArgumentParser(description=spec['name'])\n    parser.add_argument('--source-dir')\n    group = parser.add_mutually_exclusive_group(required=True)\n    group.add_argument('--input', type=Path, help='UTF-8 JSON file; no code or paths inside task input')\n    group.add_argument('--input-json', help='Inline JSON task input')\n    group.add_argument('--example', action='store_true', help='Run the declared example input')\n    group.add_argument('--describe', action='store_true', help='Read the contract without importing upstream code')\n    args = parser.parse_args()\n    try:\n        if args.describe:\n            print(json.dumps({'skill_id': spec['id'], 'interface': spec['interface'], 'example_input': spec['example']}, allow_nan=False))\n            return 0\n        if args.input:\n            with args.input.open('rb') as stream: raw=stream.read(65537)\n        else: raw=args.input_json.encode() if args.input_json else None\n        if raw is not None and len(raw) > 65536: raise ContractError('Input exceeds 64 KiB')\n        data = json.loads(raw) if raw is not None else spec['example']\n        validate_data(data, spec['interface']['input_schema'])\n        if not args.source_dir: raise ContractError('--source-dir is required for execution')\n        root = verify_source(args.source_dir, spec)\n        os.environ['MPLBACKEND'] = 'Agg'\n        os.environ.setdefault('OMP_NUM_THREADS', '1')\n        random.seed(data.get('seed', 0))\n        # Upstream diagnostic prints do not contaminate the machine response.\n        with contextlib.redirect_stdout(sys.stderr):\n            outputs, checks = backend(root, data, spec)\n        outputs, checks = clean_json(outputs), clean_json(checks)\n        validate_data(outputs, spec['interface']['output_schema'])\n        if not checks or not all(type(v) is bool for v in checks.values()): raise ContractError('Invalid postcondition report')\n        ok = all(checks.values())\n        print(json.dumps({'schema_version':'1','skill_id':spec['id'],'status':'task_complete' if ok else 'task_failed',\n            'success':ok,'outputs':outputs,'checks':checks,'execution_domain':spec['interface']['execution_domain'],\n            'source_commit':spec['source_commit'],'interface_version':'1','hardware_execution':False}, allow_nan=False, separators=(',',':')))\n        return 0 if ok else 1\n    except (ContractError, ValueError, OSError, ImportError) as error:\n        print(json.dumps({'schema_version':'1','skill_id':spec['id'],'status':'failed','success':False,\n            'reason_code':'invalid_input_or_environment','message':str(error),'hardware_execution':False}, allow_nan=False))\n        return 2\n    except Exception as error:\n        print(json.dumps({'schema_version':'1','skill_id':spec['id'],'status':'failed','success':False,\n            'reason_code':'execution_failed','message':str(error)[:1000],'hardware_execution':False}, allow_nan=False))\n        return 1\n\n\n\"\"\"Bounded adapters calling original pinned PythonRobotics functions (MIT).\nUpstream algorithms remain in their own immutable checkout; no main/GUI runs.\n\"\"\"\n\n\ndef segment_distance(point, a, b):\n    dx, dy = b[0]-a[0], b[1]-a[1]\n    t = max(0., min(1., ((point[0]-a[0])*dx+(point[1]-a[1])*dy)/(dx*dx+dy*dy))) if dx or dy else 0.\n    return math.hypot(point[0]-a[0]-t*dx, point[1]-a[1]-t*dy)\n\n\ndef path_length(path):\n    return sum(math.dist(a,b) for a,b in zip(path,path[1:]))\n\n\ndef path_checks(path, start, goal, obstacles=None, radius=0., tolerance=1e-6):\n    checks={'nonempty':len(path)>1,\n        'start_reached':bool(path) and math.dist(path[0][:2],start[:2])<=tolerance,\n        'goal_reached':bool(path) and math.dist(path[-1][:2],goal[:2])<=tolerance}\n    if obstacles is not None:\n        checks['supplied_obstacles_clear']=all(segment_distance(o,a,b)+1e-8 >= radius+(o[2] if len(o)>2 else 0.)\n            for a,b in zip(path,path[1:]) for o in obstacles)\n    return checks\n\n\ndef pythonrobotics_backend(root, d, spec):\n    import numpy as np\n    np.random.seed(d.get('seed',0))\n    m=load_upstream(root,spec['source_path'])\n    op=spec['operation']\n    if op=='grid':\n        lo,hi=d['bounds']\n        if lo>=hi or hi-lo>40: raise ContractError('bounds must increase and span at most 40 m')\n        boundary=[[x,y] for x in range(lo,hi+1) for y in (lo,hi)] + [[x,y] for y in range(lo,hi+1) for x in (lo,hi)]\n        obs=boundary+d['obstacles']\n        if any(not lo<v<hi for p in (d['start'],d['goal']) for v in p): raise ContractError('Start/goal outside open bounds')\n        if any(math.dist(p,o)<=d['robot_radius'] for p in (d['start'],d['goal']) for o in obs): raise ContractError('Start/goal in collision')\n        planner=getattr(m,spec['class'])([p[0] for p in obs],[p[1] for p in obs],1.,d['robot_radius'])\n        x,y=planner.planning(*d['start'],*d['goal'])\n        path=list(map(list,zip(x,y)))\n        if path and math.dist(path[-1],d['start'])<math.dist(path[0],d['start']): path.reverse()\n        return {'path_xy':path,'length_m':path_length(path)},path_checks(path,d['start'],d['goal'],obs,d['robot_radius'])\n    if op in ('rrt','rrt_star','rrt_sobol'):\n        if d['bounds'][0]>=d['bounds'][1]: raise ContractError('Bounds must increase')\n        if any(o[2]<=0 for o in d['obstacles']): raise ContractError('Obstacle radii must be positive')\n        cls=getattr(m,{'rrt':'RRT','rrt_star':'RRTStar','rrt_sobol':'RRTSobol'}[op])\n        planner=cls(start=d['start'],goal=d['goal'],obstacle_list=d['obstacles'],rand_area=d['bounds'],\n            expand_dis=2.,path_resolution=.1,max_iter=1500,robot_radius=d['robot_radius'])\n        path=planner.planning(animation=False) or []\n        if path and math.dist(path[-1],d['start'])<math.dist(path[0],d['start']): path.reverse()\n        return {'path_xy':path,'length_m':path_length(path)},path_checks(path,d['start'],d['goal'],d['obstacles'],d['robot_radius'])\n    if op=='prm':\n        lo,hi=d['bounds']\n        if lo>=hi or hi-lo>40: raise ContractError('Invalid bounds')\n        obs=d['obstacles']+[[x,y] for x in range(lo,hi+1) for y in (lo,hi)]+[[x,y] for y in range(lo,hi+1) for x in (lo,hi)]\n        m.N_SAMPLE=300\n        x,y=m.prm_planning(*d['start'],*d['goal'],[p[0] for p in obs],[p[1] for p in obs],d['robot_radius'],rng=np.random.default_rng(d['seed']))\n        path=list(map(list,zip(x,y)))\n        if path and math.dist(path[-1],d['start'])<math.dist(path[0],d['start']): path.reverse()\n        return {'path_xy':path,'length_m':path_length(path)},path_checks(path,d['start'],d['goal'],obs,d['robot_radius'])\n    if op in ('dubins','reeds_shepp'):\n        fn=m.plan_dubins_path if op=='dubins' else m.reeds_shepp_path_planning\n        x,y,yaw,modes,lengths=fn(*d['start'],*d['goal'],d['curvature'],step_size=d['step_size'])\n        path=list(map(list,zip(x,y))) if x is not None else []\n        checks=path_checks(path,d['start'],d['goal'],tolerance=1e-5)\n        checks['goal_heading']=bool(path) and abs(math.atan2(math.sin(yaw[-1]-d['goal'][2]),math.cos(yaw[-1]-d['goal'][2])))<1e-5\n        return {'path_xy':path,'yaw_rad':[] if yaw is None else list(yaw),'length_m':path_length(path)},checks\n    if op in ('cubic','bspline','bezier','catmull'):\n        pts=np.array(d['waypoints'])\n        if any(math.dist(a,b)<1e-6 for a,b in zip(pts,pts[1:])): raise ContractError('Adjacent waypoints must differ')\n        if op=='cubic':\n            spline=m.CubicSpline2D(pts[:,0],pts[:,1])\n            samples=np.linspace(0,spline.s[-1]-1e-10,d['samples'])\n            path=[spline.calc_position(float(t)) for t in samples]\n        elif op=='bspline':\n            x,y,*_=m.interpolate_b_spline_path(pts[:,0],pts[:,1],d['samples'],degree=min(3,len(pts)-1))\n            path=list(zip(x,y))\n        elif op=='bezier': path=m.calc_bezier_path(pts,n_points=d['samples'])\n        else:\n            x,y=m.catmull_rom_spline(pts,d['samples']); path=list(zip(x,y))\n        path=np.asarray(path).tolist()\n        return {'path_xy':path,'length_m':path_length(path)},path_checks(path,d['waypoints'][0],d['waypoints'][-1],tolerance=1e-6)\n    if op=='quintic':\n        p=m.QuinticPolynomial(*d['start'],*d['goal'],d['duration'])\n        t=np.linspace(0,d['duration'],d['samples'])\n        position=[p.calc_point(v) for v in t]; velocity=[p.calc_first_derivative(v) for v in t]; acceleration=[p.calc_second_derivative(v) for v in t]\n        return {'time_s':t,'position_m':position,'velocity_m_s':velocity,'acceleration_m_s2':acceleration}, {\n            'initial_state':max(abs(a[0]-b) for a,b in zip([position,velocity,acceleration],d['start']))<1e-7,\n            'terminal_state':max(abs(a[-1]-b) for a,b in zip([position,velocity,acceleration],d['goal']))<1e-7}\n    if op in ('pure_pursuit','stanley'):\n        path=np.asarray(d['path_xy']); state=m.State(*d['state'])\n        if op=='pure_pursuit':\n            course=m.TargetCourse(path[:,0],path[:,1]); steer,index=m.pure_pursuit_steer_control(state,course,0)\n        else:\n            yaw=np.arctan2(np.gradient(path[:,1]),np.gradient(path[:,0]))\n            steer,index=m.stanley_control(state,path[:,0],path[:,1],yaw,0)\n        return {'steering_rad':float(steer),'target_index':int(index)}, {'finite_command':bool(np.isfinite(steer)),'target_in_path':0<=int(index)<len(path)}\n    if op=='dwa':\n        cfg=m.Config(); cfg.robot_radius=d['robot_radius']; cfg.robot_type=m.RobotType.circle\n        control,trajectory=m.dwa_control(np.asarray(d['state'],dtype=float),cfg,np.array(d['goal']),np.array(d['obstacles']))\n        return {'control_v_yawrate':control,'trajectory_xy_yaw_v_w':trajectory}, {\n            'velocity_bounds':bool(cfg.min_speed<=control[0]<=cfg.max_speed and abs(control[1])<=cfg.max_yaw_rate+1e-8),\n            'collision_free_samples':all(math.dist(p[:2],o)>=cfg.robot_radius-1e-8 for p in trajectory for o in d['obstacles'])}\n    if op in ('ekf','ekf_velocity','ukf'):\n        dimension=5 if op=='ekf_velocity' else 4\n        x=np.array(d['state']).reshape(dimension,1); cov=np.array(d['covariance']); z=np.array(d['measurement_xy']).reshape(2,1); u=np.array(d['control_v_yawrate']).reshape(2,1)\n        if not np.allclose(cov,cov.T,atol=1e-10) or np.linalg.eigvalsh(cov).min()<=0: raise ContractError('Covariance must be symmetric positive definite')\n        if op=='ukf': state,covariance=m.ukf_estimation(x,cov,z,u,*m.setup_ukf(4))\n        else: state,covariance=m.ekf_estimation(x,cov,z,u)\n        return {'state_xy_yaw_v_scale' if op=='ekf_velocity' else 'state_xy_yaw_v':state.reshape(-1),'covariance':covariance}, {\n            'finite_state':bool(np.isfinite(state).all()), 'symmetric_covariance':bool(np.allclose(covariance,covariance.T,atol=1e-8)),\n            'positive_semidefinite_covariance':bool(np.linalg.eigvalsh(covariance).min()>=-1e-8)}\n    if op in ('fk','ik'):\n        lengths=np.array(d['link_lengths']); angles=np.array(d['joint_angles'])\n        if len(lengths)!=len(angles) or any(lengths<=0): raise ContractError('Positive link lengths must match joint count')\n        m.N_LINKS=len(lengths)\n        if op=='ik':\n            if np.linalg.norm(d['target_xy'])>sum(lengths): raise ContractError('Target outside reach')\n            m.N_ITERATIONS=1000; angles,solved=m.inverse_kinematics(lengths,angles,np.array(d['target_xy']))\n        else: solved=True\n        endpoint=m.forward_kinematics(lengths,angles)\n        independent=np.array([sum(lengths*np.cos(np.cumsum(angles))),sum(lengths*np.sin(np.cumsum(angles)))])\n        checks={'forward_kinematics_agrees':bool(np.linalg.norm(endpoint-independent)<1e-8)}\n        if op=='ik': checks['target_residual_below_0_1_m']=bool(solved and np.linalg.norm(endpoint-np.array(d['target_xy']))<.1)\n        return {'joint_angles_rad':angles,'end_effector_xy_m':endpoint},checks\n    if op=='lqr':\n        A,B=m.get_model_matrix(); K,P,eigs=m.dlqr(A,B,np.diag(d['state_cost_diagonal']),np.array([[d['force_cost']]]))\n        force=-K@np.array(d['state']).reshape(4,1)\n        return {'force_n':float(force.item()),'gain':K.tolist()[0],'closed_loop_spectral_radius':float(max(abs(eigs)))}, {'stable_discrete_linear_model':bool(max(abs(eigs))<1.)}\n    if op=='potential':\n        if not d['obstacles']: raise ContractError('Potential-field source requires obstacle points')\n        x,y=m.potential_field_planning(*d['start'],*d['goal'],[p[0] for p in d['obstacles']],[p[1] for p in d['obstacles']],.5,d['robot_radius'])\n        path=list(map(list,zip(x,y)))\n        return {'path_xy':path,'length_m':path_length(path)},path_checks(path,d['start'],d['goal'],d['obstacles'],d['robot_radius'],tolerance=.5)\n    if op=='smoothing':\n        if not all(path_checks(d['path_xy'],d['path_xy'][0],d['path_xy'][-1],d['obstacles'],d['robot_radius']).values()):\n            raise ContractError('Input path must clear every supplied inflated obstacle')\n        # Upstream shortcut selection remains unchanged. Replace its sampled\n        # collision test and off-by-one arclength interpolation at the boundary.\n        def exact_clear(first, second, obstacles, robot_radius=0., sample_step=.2):\n            return all(segment_distance(o,first,second)>robot_radius+o[2] for o in obstacles)\n        def arclength_point(path, distance):\n            remaining=distance\n            for index,(a,b) in enumerate(zip(path,path[1:])):\n                length=math.dist(a,b)\n                if length and remaining<=length:\n                    ratio=remaining/length\n                    return [a[0]+ratio*(b[0]-a[0]),a[1]+ratio*(b[1]-a[1]),index]\n                remaining-=length\n            return [path[-1][0],path[-1][1],len(path)-2]\n        m.line_collision_check=exact_clear\n        m.get_target_point=arclength_point\n        path=m.path_smoothing(d['path_xy'],100,d['obstacles'],robot_radius=d['robot_radius'])\n        checks=path_checks(path,d['path_xy'][0],d['path_xy'][-1],d['obstacles'],d['robot_radius'])\n        checks['not_longer']=path_length(path)<=path_length(d['path_xy'])+1e-8\n        return {'path_xy':path,'length_m':path_length(path)},checks\n    raise ContractError('Unknown reviewed operation')\n\nSPEC = json.loads('{\"id\":\"pythonrobotics-dijkstra-planner\",\"name\":\"Dijkstra collision-checked grid path\",\"operation\":\"grid\",\"source_path\":\"PathPlanning/Dijkstra/dijkstra.py\",\"source_commit\":\"cdd0cc888802b584c2d654ca85e3c5460973487d\",\"source_hashes\":{\"ArmNavigation/n_joint_arm_to_point_control/NLinkArm.py\":\"0050d72932952f05992c0195bdcc57cfb9dfaa13be81daf9e22c6344a8e5735d\",\"ArmNavigation/n_joint_arm_to_point_control/n_joint_arm_to_point_control.py\":\"7ef0e52b7f949b84d82d5ea2824cd3dbc0f60e8e21dd8a9499b407231a310b1d\",\"InvertedPendulum/inverted_pendulum_lqr_control.py\":\"8af1d6540a40866fdb35822e3eea9042a58d43c4df28d4ebd7beec22593d1b55\",\"LICENSE\":\"ca6f10832a25c1cd07fa62872458ca336a6ea83ee2f2236216086bd7a5bbf264\",\"Localization/extended_kalman_filter/ekf_with_velocity_correction.py\":\"58bb755f0f917b07d20dace4750aca16312c19e5f897e7e7d376732acd5e4cfe\",\"Localization/extended_kalman_filter/extended_kalman_filter.py\":\"fa9bb52dbb63e6e959ba4e7bcf20cbe8d6ce69e88453ad0f605a21fc6979a28e\",\"Localization/unscented_kalman_filter/unscented_kalman_filter.py\":\"ec89f8bd08c45d1dca15f41a7313dfa402679e73ad7290293ab7ea2d2fcce94e\",\"PathPlanning/AStar/a_star.py\":\"387293061a8509557bb80a02a0f279b0b2b4629c0189db661a93fee5d9e0b74a\",\"PathPlanning/BSplinePath/bspline_path.py\":\"5d45a5400b684b59ef0e3ed1f780f9ba1ad069e1fde3879abf2f21307b77aa94\",\"PathPlanning/BezierPath/bezier_path.py\":\"4f3bb51aba78a47ab4665b7ccfbbd70acf75607bbdd5f1726a34962ecbc46f5d\",\"PathPlanning/BidirectionalAStar/bidirectional_a_star.py\":\"6870b9bf1666b8d31ba0ffdfe92c9a715dcfdb3a40398e551352a3180da9dc01\",\"PathPlanning/BidirectionalBreadthFirstSearch/bidirectional_breadth_first_search.py\":\"c0536f0296ddefd700b9d754e936949087d822d65901da462971ec2d82a9183c\",\"PathPlanning/BreadthFirstSearch/breadth_first_search.py\":\"960bfbc9e15b81df08d78c8266e8467378f307b5fec9a58b8de2c4bb8feab069\",\"PathPlanning/Catmull_RomSplinePath/catmull_rom_spline_path.py\":\"14d5f7046302cac828ebb9bbd338ef3e06952d79ae112415aa61369901b8af90\",\"PathPlanning/CubicSpline/cubic_spline_planner.py\":\"eb416e3834f5881dbec824729a1ab6010138f49a030ceb29f758b23cc5ed2adc\",\"PathPlanning/DepthFirstSearch/depth_first_search.py\":\"60147610b72c80ce8385f667f4a4e874b60421c5e40f19775f087693248f4afb\",\"PathPlanning/Dijkstra/dijkstra.py\":\"5dceeacad5a3a03245bea9fb9b344252a51a5a1d364aa76d83a8832d975d6574\",\"PathPlanning/DubinsPath/dubins_path_planner.py\":\"07fba67057d900d4c2b7794c9b0818a86342af8a520c914252dec620d94c61eb\",\"PathPlanning/DynamicWindowApproach/dynamic_window_approach.py\":\"985aa9acb1e6de234fa15ec9c83d5a7435ce2d9e22c6f6e308e1ab038141c471\",\"PathPlanning/GreedyBestFirstSearch/greedy_best_first_search.py\":\"5b166b9b6a62f1f40e0d018ff509a90d07ce40471f99e2acf2e9f0d5d32de434\",\"PathPlanning/PotentialFieldPlanning/potential_field_planning.py\":\"79bc4c9b5cae8266d3bf4fee05385ea4dd1094f24cdcb09a3b1b4615d3719f01\",\"PathPlanning/ProbabilisticRoadMap/probabilistic_road_map.py\":\"7a1ae0ff436bcdf203853449a56761841a57f16a288520d2727284daa9ee3691\",\"PathPlanning/QuinticPolynomialsPlanner/quintic_polynomials_planner.py\":\"606e96761b7c1590afba5824bf9e3a23c575690a0a16b2256b8a23dae7797272\",\"PathPlanning/RRT/rrt.py\":\"ed8844dba554c6a27cc18137c2214dd066145701216d0aeab974cc4e4fea5d6e\",\"PathPlanning/RRT/rrt_with_pathsmoothing.py\":\"3d5aba05cccd5c065fbdebe24f175cecef3979e924ff637b8afe75ec1387c130\",\"PathPlanning/RRTStar/rrt_star.py\":\"6beeefcc0c8b9755f0257fd442e9b0a35dac1d913fb56a8bf8ce973ba5a20b25\",\"PathPlanning/ReedsSheppPath/reeds_shepp_path_planning.py\":\"7b2bc055cd9de93a95d9ed4a000bad049ccc889324600d3736ed4da18f7ea947\",\"PathTracking/pure_pursuit/pure_pursuit.py\":\"d6702311927cb44cbe27546e6e76ba0b7371a6b0715013ac2a40735b299779eb\",\"PathTracking/stanley_control/stanley_control.py\":\"ec426a511821620d798a46a35f0ab64382bd79ff417e0f59fb0c1f7873d8c7a0\",\"utils/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"utils/angle.py\":\"62c3f0fdc76298d772d574b7a4357fded9265d1e0af048d87b5c8ca1a528017a\",\"utils/plot.py\":\"7958fb8403bd3b05550a0356e3aeaeaee194678dfb8a069350a089b0666e8537\",\"ArmNavigation/n_joint_arm_to_point_control/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"ArmNavigation/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"Localization/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"PathPlanning/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"PathPlanning/CubicSpline/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"PathPlanning/DubinsPath/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"PathPlanning/RRT/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"PathPlanning/RRTStar/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"PathTracking/__init__.py\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"},\"interface\":{\"version\":\"1\",\"kind\":\"algorithm\",\"abstraction_level\":0,\"execution_domain\":\"computation\",\"language\":\"python\",\"input_transport\":\"json_file_or_inline\",\"output_transport\":\"json_stdout\",\"conventions\":{\"linear_unit\":\"m\",\"angular_unit\":\"rad\",\"time_unit\":\"s\",\"coordinate_frame\":\"planar_map_xy\",\"joint_order\":\"not_used\",\"quaternion_order\":\"not_used\"},\"input_schema\":{\"type\":\"object\",\"required\":[\"start\",\"goal\",\"bounds\",\"obstacles\",\"robot_radius\"],\"additionalProperties\":false,\"properties\":{\"start\":{\"type\":\"array\",\"minItems\":2,\"maxItems\":2,\"items\":{\"type\":\"number\",\"minimum\":1,\"maximum\":39},\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"x-order\":[\"x\",\"y\"],\"description\":\"start; units=m; frame=planar_map_xy; component order=x,y\"},\"goal\":{\"type\":\"array\",\"minItems\":2,\"maxItems\":2,\"items\":{\"type\":\"number\",\"minimum\":1,\"maximum\":39},\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"x-order\":[\"x\",\"y\"],\"description\":\"goal; units=m; frame=planar_map_xy; component order=x,y\"},\"bounds\":{\"type\":\"array\",\"minItems\":2,\"maxItems\":2,\"items\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":40},\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"description\":\"bounds; units=m; frame=planar_map_xy\"},\"obstacles\":{\"type\":\"array\",\"minItems\":0,\"maxItems\":200,\"items\":{\"type\":\"array\",\"minItems\":2,\"maxItems\":2,\"items\":{\"type\":\"number\",\"minimum\":0,\"maximum\":40}},\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"x-order\":[\"x\",\"y\"],\"description\":\"obstacles; units=m; frame=planar_map_xy; component order=x,y\"},\"robot_radius\":{\"type\":\"number\",\"minimum\":0.1,\"maximum\":0.6,\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"description\":\"robot_radius; units=m; frame=planar_map_xy\"}}},\"output_schema\":{\"type\":\"object\",\"required\":[\"path_xy\",\"length_m\"],\"additionalProperties\":false,\"properties\":{\"path_xy\":{\"type\":\"array\",\"minItems\":2,\"maxItems\":2048,\"items\":{\"type\":\"array\",\"minItems\":2,\"maxItems\":2,\"items\":{\"type\":\"number\",\"minimum\":-100.0,\"maximum\":100.0}},\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"x-order\":[\"x\",\"y\"],\"description\":\"path_xy; units=m; frame=planar_map_xy; component order=x,y\"},\"length_m\":{\"type\":\"number\",\"minimum\":0,\"maximum\":100000,\"x-unit\":\"m\",\"x-frame\":\"planar_map_xy\",\"description\":\"length_m; units=m; frame=planar_map_xy\"}}},\"preconditions\":[\"Inputs use the declared units and frame; no implicit transforms.\",\"This exact numerical/simulation model is not a physical robot configuration.\"],\"postconditions\":[\"All declared checks must pass and all outputs must be finite.\",\"Success applies only to this operation and model, not higher-level task completion.\"],\"failure_codes\":[\"invalid_input_or_environment\",\"execution_failed\",\"task_failed\"],\"composition\":{\"depends_on\":[],\"automatic_frame_conversion\":false,\"automatic_hardware_execution\":false},\"output_selector\":\"outputs\"},\"example\":{\"start\":[2,2],\"goal\":[18,18],\"bounds\":[0,20],\"obstacles\":[[10,1],[10,2],[10,3],[10,4],[10,5],[10,6],[10,7],[10,8],[10,9],[10,10],[10,11],[10,12]],\"robot_radius\":0.3},\"class\":\"DijkstraPlanner\"}')\n\nif __name__ == \"__main__\":\n    raise SystemExit(adapter_main(SPEC, pythonrobotics_backend))\n",
      "requirements.txt": "contourpy==1.3.2\ncycler==0.12.1\nfonttools==4.65.0\nkiwisolver==1.5.1\nmatplotlib==3.9.4\nnumpy==1.26.4\npackaging==26.3\npillow==12.3.0\npyparsing==3.3.3\npython-dateutil==2.9.0.post0\nscipy==1.13.1\nsix==1.17.0\n"
    }
  },
  "evidence": {
    "level": "task_complete",
    "verified_by": "Hub maintainer runtime harness",
    "date": "2026-10-19",
    "environment": {
      "scope": "computation",
      "platforms": [
        "linux",
        "windows"
      ],
      "physical_robot": false
    },
    "commands": [
      "python tools/verify_executable_recipes.py --source-cache SOURCE_CACHE --output OUTPUT --backend pythonrobotics"
    ],
    "result": "Three frozen positive cases passed against this exact adapter; see per-case receipts. This does not estimate general accuracy.",
    "artifacts": [
      "https://raw.githubusercontent.com/Superfast-Robotics/robot-skill-hub/39b06d7a6016edcea27e348e49ffd86f29ec1811/evidence/runtime/executable-conversions-v1/pythonrobotics-dijkstra-planner.json"
    ],
    "runtime_verification": "task_complete",
    "contributor_reported": null,
    "hub_verification": {
      "runtime": "task_complete",
      "scope": "computation",
      "case_count": 3,
      "source_unchanged": true
    }
  },
  "maintenance": {
    "record_version": "1.0.0",
    "last_checked": "2026-10-19",
    "upstream_status": "immutable_source_and_explicit_dependency_versions"
  },
  "contribution": {
    "allowed_paths": [
      "catalog/records/pythonrobotics-dijkstra-planner.json",
      "docs/recipes/pythonrobotics-dijkstra-planner.md",
      "recipes/pythonrobotics-dijkstra-planner/run.py",
      "recipes/pythonrobotics-dijkstra-planner/requirements.txt"
    ],
    "instructions": "Preserve upstream origin/license and interface semantics. Edit the finite manifest/templates and regenerate; record new exact-byte evidence for behavior changes."
  }
}
