Compare commits
7 Commits
BugFixFull
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e24fe3b512 | ||
|
|
6c07d62cbe | ||
|
|
6d07c24c71 | ||
| c84b4067d6 | |||
| 76416aae95 | |||
| 2ef80e8878 | |||
|
|
0b39710a78 |
98
README.md
@@ -1,36 +1,90 @@
|
|||||||
# Evil nKode
|
# Evilnkode Project
|
||||||
|
|
||||||
Simulated nKode Cracker
|
This README provides instructions for setting up and running the `evilnkode` project using Conda, activating the environment, and executing the provided CLI scripts. It also covers how to access help for command-line options.
|
||||||
|
|
||||||
## Installation
|
## Prerequisites
|
||||||
|
|
||||||
- Python version 3.10 or greater is required
|
- **Conda**: Ensure you have Conda installed (Miniconda or Anaconda). Download from [conda.io](https://conda.io).
|
||||||
- Install anaconda (or your preferred tool for environment management)
|
|
||||||
|
## Setting Up the Environment
|
||||||
|
|
||||||
|
To set up the project environment using the provided `environment.yaml` file, follow these steps:
|
||||||
|
|
||||||
|
1. **Install the environment**:
|
||||||
|
- Ensure you are in the project root directory where `environment.yaml` is located.
|
||||||
|
- Run the following command to create the `evilnkode` environment:
|
||||||
|
```bash
|
||||||
|
conda env create -f environment.yaml
|
||||||
|
```
|
||||||
|
- This will install all dependencies specified in `environment.yaml`.
|
||||||
|
|
||||||
|
## Activating the Environment
|
||||||
|
|
||||||
|
To activate the `evilnkode` environment, run:
|
||||||
|
|
||||||
### Using conda
|
|
||||||
```bash
|
```bash
|
||||||
conda env create -f environment.yml
|
conda activate evilnkode
|
||||||
conda activate pynkode
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Starting a Jupyter Notebook
|
Once activated, your terminal prompt should change to include `(evilnkode)`, indicating the environment is active.
|
||||||
|
|
||||||
|
## Running CLI Scripts
|
||||||
|
|
||||||
|
The project includes two main CLI scripts: `cli.visualnkode` and `cli.benchmark_histogram`. Below are instructions to run each.
|
||||||
|
|
||||||
|
### Running `cli.visualnkode`
|
||||||
|
|
||||||
|
To execute the `visualnkode` CLI script:
|
||||||
|
|
||||||
### Option 1: Using classic Jupyter Notebook
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure your environment is activated
|
python -m cli.visualnkode
|
||||||
# For conda: conda activate pynkode
|
|
||||||
# For pyenv: (should be automatic if in the directory)
|
|
||||||
|
|
||||||
# Start the Jupyter Notebook server
|
|
||||||
jupyter notebook
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option 2: Using JupyterLab
|
- This command runs the `visualnkode` module from the `cli` package.
|
||||||
|
- To view available options and arguments, use the `-help` flag:
|
||||||
|
```bash
|
||||||
|
python -m cli.visualnkode -help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running `cli.benchmark_histogram`
|
||||||
|
|
||||||
|
To execute the `benchmark_histogram` CLI script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure your environment is activated
|
python -m cli.benchmark_histogram
|
||||||
# Start JupyterLab
|
|
||||||
jupyter lab
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notebooks
|
- This command runs the `benchmark_histogram` module, which may generate output such as benchmark results or histograms. For example, it might produce output like:
|
||||||
- [evilnkode](notebooks/evilkode.ipynb)
|
```
|
||||||
|
File exists: output/slidingtowershufflekeypad-6-8-4-5-4-4-10000/benchmark/slidingtowershufflekeypad-6-8-4-5-4-4-10000.pkl
|
||||||
|
Bench SlidingTowerShuffle Break 5
|
||||||
|
Bench SlidingTowerShuffle Replay 5
|
||||||
|
```
|
||||||
|
- To view available options and arguments, use the `-help` flag:
|
||||||
|
```bash
|
||||||
|
python -m cli.benchmark_histogram -help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the `-help` Flag
|
||||||
|
|
||||||
|
Both CLI scripts (`cli.visualnkode` and `cli.benchmark_histogram`) support a `-help` flag to display available command-line options and their descriptions. Run the following to explore options for each script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m cli.visualnkode -help
|
||||||
|
python -m cli.benchmark_histogram -help
|
||||||
|
```
|
||||||
|
|
||||||
|
This will provide detailed information about parameters, flags, and usage for each script.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
Key files and directories in the project:
|
||||||
|
|
||||||
|
- `environment.yaml`: Conda environment configuration file.
|
||||||
|
- `cli/`: Contains CLI scripts (`visualnkode` and `benchmark_histogram`).
|
||||||
|
- `output/`: Directory where script outputs, such as benchmark results, are stored.
|
||||||
|
- `src/`: Source code for the project.
|
||||||
|
- `tests/`: Test scripts for the project.
|
||||||
|
- `requirements.txt`: Additional dependencies (if needed outside Conda).
|
||||||
|
|
||||||
|
For further details, explore the project documentation in the `docs/` directory.
|
||||||
|
|||||||
0
cli/__init__.py
Normal file
115
cli/benchmark_histogram.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import argparse
|
||||||
|
from src.benchmark import benchmark
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from pathlib import Path
|
||||||
|
from statistics import mean
|
||||||
|
from src.keypad.keypad import (
|
||||||
|
RandomSplitShuffleKeypad,
|
||||||
|
RandomShuffleKeypad,
|
||||||
|
SlidingSplitShuffleKeypad,
|
||||||
|
SlidingTowerShuffleKeypad,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bench_histogram(data, title, number_of_keys, properties_per_key, passcode_len, max_tries_before_lockout, complexity,
|
||||||
|
disparity, run_count, save_path: Path = None):
|
||||||
|
min_val = min(data)
|
||||||
|
max_val = max(data)
|
||||||
|
bins = range(min_val, max_val + 2)
|
||||||
|
plt.hist(data, bins=bins, edgecolor='black')
|
||||||
|
plt.title(title)
|
||||||
|
plt.xlabel('# of Login Observations')
|
||||||
|
plt.ylabel('Simulations')
|
||||||
|
text = (f"number_of_keys={number_of_keys}\n"
|
||||||
|
f"properties_per_key={properties_per_key}\n"
|
||||||
|
f"passcode_len={passcode_len}\n"
|
||||||
|
f"max_tries_before_lockout={max_tries_before_lockout}\n"
|
||||||
|
f"complexity={complexity}\n"
|
||||||
|
f"disparity={disparity}\n"
|
||||||
|
f"run_count={run_count}")
|
||||||
|
plt.text(0.95, 0.95, text, transform=plt.gca().transAxes, fontsize=10,
|
||||||
|
verticalalignment='top', horizontalalignment='right', bbox=dict(facecolor='white', alpha=0.5))
|
||||||
|
if save_path:
|
||||||
|
save_path = save_path / "histogram"
|
||||||
|
save_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
filename = (f"{title.replace(' ', '_')}_keys{number_of_keys}_"
|
||||||
|
f"props{properties_per_key}_pass{passcode_len}_tries{max_tries_before_lockout}_"
|
||||||
|
f"comp{complexity}_disp{disparity}_runs{run_count}.png")
|
||||||
|
plt.savefig(save_path / filename, bbox_inches='tight', dpi=300)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Benchmark Keypad Shuffle')
|
||||||
|
parser.add_argument('--shuffle_type', type=str,
|
||||||
|
choices=['RandomSplitShuffle', 'RandomShuffle', 'SlidingSplitShuffle', 'SlidingTowerShuffle'],
|
||||||
|
default='SlidingTowerShuffle', help='Type of keypad shuffle')
|
||||||
|
parser.add_argument('--number_of_keys', type=int, default=6, help='Number of keys')
|
||||||
|
parser.add_argument('--properties_per_key', type=int, default=8, help='Properties per key')
|
||||||
|
parser.add_argument('--passcode_len', type=int, default=4, help='Passcode length')
|
||||||
|
parser.add_argument('--max_tries_before_lockout', type=int, default=5, help='Max tries before lockout')
|
||||||
|
parser.add_argument('--complexity', type=int, default=4, help='Complexity')
|
||||||
|
parser.add_argument('--disparity', type=int, default=4, help='Disparity')
|
||||||
|
parser.add_argument('--run_count', type=int, default=10000, help='Number of runs')
|
||||||
|
parser.add_argument('--output_dir', type=str, default='./output',
|
||||||
|
help='Output directory for histograms')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
shuffle_classes = {
|
||||||
|
'RandomSplitShuffle': RandomSplitShuffleKeypad,
|
||||||
|
'RandomShuffle': RandomShuffleKeypad,
|
||||||
|
'SlidingSplitShuffle': SlidingSplitShuffleKeypad,
|
||||||
|
'SlidingTowerShuffle': SlidingTowerShuffleKeypad
|
||||||
|
}
|
||||||
|
|
||||||
|
keypad_class = shuffle_classes[args.shuffle_type]
|
||||||
|
keypad = keypad_class.new_keypad(args.number_of_keys, args.properties_per_key)
|
||||||
|
|
||||||
|
shuffle_type = str(type(keypad)).lower().split('.')[-1].replace("'>", "")
|
||||||
|
run_name = f"{shuffle_type}-{args.number_of_keys}-{args.properties_per_key}-{args.passcode_len}-{args.max_tries_before_lockout}-{args.complexity}-{args.disparity}-{args.run_count}"
|
||||||
|
save_path = Path(args.output_dir) / run_name
|
||||||
|
bench_result = benchmark(
|
||||||
|
number_of_keys=args.number_of_keys,
|
||||||
|
properties_per_key=args.properties_per_key,
|
||||||
|
passcode_len=args.passcode_len,
|
||||||
|
max_tries_before_lockout=args.max_tries_before_lockout,
|
||||||
|
run_count=args.run_count,
|
||||||
|
complexity=args.complexity,
|
||||||
|
disparity=args.disparity,
|
||||||
|
keypad=keypad,
|
||||||
|
file_path=save_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Bench {args.shuffle_type} Break {mean(bench_result.iterations_to_break)}")
|
||||||
|
print(f"Bench {args.shuffle_type} Replay {mean(bench_result.iterations_to_replay)}")
|
||||||
|
|
||||||
|
bench_histogram(
|
||||||
|
bench_result.iterations_to_break,
|
||||||
|
f"{args.shuffle_type} Break",
|
||||||
|
args.number_of_keys,
|
||||||
|
args.properties_per_key,
|
||||||
|
args.passcode_len,
|
||||||
|
args.max_tries_before_lockout,
|
||||||
|
args.complexity,
|
||||||
|
args.disparity,
|
||||||
|
args.run_count,
|
||||||
|
save_path
|
||||||
|
)
|
||||||
|
|
||||||
|
bench_histogram(
|
||||||
|
bench_result.iterations_to_replay,
|
||||||
|
f"{args.shuffle_type} Replay",
|
||||||
|
args.number_of_keys,
|
||||||
|
args.properties_per_key,
|
||||||
|
args.passcode_len,
|
||||||
|
args.max_tries_before_lockout,
|
||||||
|
args.complexity,
|
||||||
|
args.disparity,
|
||||||
|
args.run_count,
|
||||||
|
save_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,45 +1,41 @@
|
|||||||
import json
|
import json
|
||||||
|
from typing import Iterable
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict
|
||||||
from evilkode import Observation
|
from src.evilnkode import Observation
|
||||||
from utils import observations, passcode_generator, ShuffleTypes
|
from src.utils import observations, passcode_generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
from typing import Iterable
|
from src.keypad.keypad import (
|
||||||
|
BaseKeypad,
|
||||||
|
SlidingSplitShuffleKeypad,
|
||||||
|
SlidingTowerShuffleKeypad,
|
||||||
|
RandomShuffleKeypad,
|
||||||
|
RandomSplitShuffleKeypad,
|
||||||
|
)
|
||||||
|
import argparse
|
||||||
|
|
||||||
# Project root = parent of *this* file's directory
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
||||||
OUTPUT_DIR = PROJECT_ROOT / "example" / "obs_json"
|
|
||||||
PNG_DIR = PROJECT_ROOT / "example" / "obs_png"
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ObservationSequence:
|
class ObservationSequence:
|
||||||
target_passcode: list[int]
|
target_passcode: list[int]
|
||||||
observations: list[Observation]
|
observations: list[Observation]
|
||||||
|
|
||||||
|
|
||||||
def new_observation_sequence(
|
def new_observation_sequence(
|
||||||
number_of_keys: int,
|
keypad: BaseKeypad,
|
||||||
properties_per_key: int,
|
|
||||||
passcode_len: int,
|
passcode_len: int,
|
||||||
complexity: int,
|
complexity: int,
|
||||||
disparity: int,
|
disparity: int,
|
||||||
numb_runs: int,
|
numb_runs: int,
|
||||||
) -> ObservationSequence:
|
) -> ObservationSequence:
|
||||||
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
|
passcode = passcode_generator(keypad.k, keypad.p, passcode_len, complexity, disparity)
|
||||||
obs_seq = ObservationSequence(target_passcode=passcode, observations=[])
|
|
||||||
obs_gen = observations(
|
obs_gen = observations(
|
||||||
|
keypad=keypad,
|
||||||
target_passcode=passcode,
|
target_passcode=passcode,
|
||||||
number_of_keys=number_of_keys,
|
|
||||||
properties_per_key=properties_per_key,
|
|
||||||
min_complexity=complexity,
|
|
||||||
min_disparity=disparity,
|
|
||||||
shuffle_type=ShuffleTypes.SPLIT_SHUFFLE,
|
|
||||||
number_of_observations=numb_runs,
|
number_of_observations=numb_runs,
|
||||||
)
|
)
|
||||||
for obs in obs_gen:
|
return ObservationSequence(target_passcode=passcode, observations=[obs for obs in obs_gen])
|
||||||
obs.keypad = obs.keypad.tolist()
|
|
||||||
obs_seq.observations.append(obs)
|
|
||||||
|
|
||||||
return obs_seq
|
|
||||||
|
|
||||||
def _next_json_filename(base_dir: Path) -> Path:
|
def _next_json_filename(base_dir: Path) -> Path:
|
||||||
"""Find the next available observation_X.json file in base_dir."""
|
"""Find the next available observation_X.json file in base_dir."""
|
||||||
@@ -50,22 +46,15 @@ def _next_json_filename(base_dir: Path) -> Path:
|
|||||||
return candidate
|
return candidate
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
def save_observation_sequence_to_json(seq: ObservationSequence, filename: Path | None = None) -> None:
|
|
||||||
"""
|
def save_observation_sequence_to_json(seq: ObservationSequence,
|
||||||
Save ObservationSequence to JSON.
|
filename: Path) -> None:
|
||||||
- If filename is None, put it under PROJECT_ROOT/output/obs_json/ as observation_{n}.json
|
|
||||||
- Creates directory if needed
|
|
||||||
"""
|
|
||||||
if filename is None:
|
|
||||||
base_dir = OUTPUT_DIR
|
|
||||||
base_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
filename = _next_json_filename(base_dir)
|
|
||||||
else:
|
|
||||||
filename.parent.mkdir(parents=True, exist_ok=True)
|
filename.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with filename.open("w", encoding="utf-8") as f:
|
with filename.open("w", encoding="utf-8") as f:
|
||||||
json.dump(asdict(seq), f, indent=4)
|
json.dump(asdict(seq), f, indent=4)
|
||||||
|
|
||||||
|
|
||||||
# ---------- Helpers ----------
|
# ---------- Helpers ----------
|
||||||
def _load_font(preferred: str, size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
def _load_font(preferred: str, size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
||||||
"""Try a preferred TTF, fall back to common monospace, then PIL default."""
|
"""Try a preferred TTF, fall back to common monospace, then PIL default."""
|
||||||
@@ -83,14 +72,17 @@ def _load_font(preferred: str, size: int) -> ImageFont.FreeTypeFont | ImageFont.
|
|||||||
continue
|
continue
|
||||||
return ImageFont.load_default()
|
return ImageFont.load_default()
|
||||||
|
|
||||||
|
|
||||||
def _text_size(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.ImageFont) -> tuple[int, int]:
|
def _text_size(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.ImageFont) -> tuple[int, int]:
|
||||||
"""Get (w, h) using font bbox for accurate layout."""
|
"""Get (w, h) using font bbox for accurate layout."""
|
||||||
left, top, right, bottom = draw.textbbox((0, 0), text, font=font)
|
left, top, right, bottom = draw.textbbox((0, 0), text, font=font)
|
||||||
return right - left, bottom - top
|
return int(right - left), int(bottom - top)
|
||||||
|
|
||||||
|
|
||||||
def _join_nums(nums: Iterable[int]) -> str:
|
def _join_nums(nums: Iterable[int]) -> str:
|
||||||
return " ".join(str(n) for n in nums)
|
return " ".join(str(n) for n in nums)
|
||||||
|
|
||||||
|
|
||||||
def _next_available_path(path: Path) -> Path:
|
def _next_available_path(path: Path) -> Path:
|
||||||
"""If path exists, append _1, _2, ..."""
|
"""If path exists, append _1, _2, ..."""
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
@@ -103,6 +95,7 @@ def _next_available_path(path: Path) -> Path:
|
|||||||
return candidate
|
return candidate
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
# ---------- Core rendering ----------
|
# ---------- Core rendering ----------
|
||||||
def render_observation_to_png(
|
def render_observation_to_png(
|
||||||
target_passcode: list[int],
|
target_passcode: list[int],
|
||||||
@@ -213,6 +206,7 @@ def render_observation_to_png(
|
|||||||
|
|
||||||
img.save(out_path, format="PNG")
|
img.save(out_path, format="PNG")
|
||||||
|
|
||||||
|
|
||||||
def _next_run_dir(base_dir: Path) -> Path:
|
def _next_run_dir(base_dir: Path) -> Path:
|
||||||
"""Find the next available run directory under base_dir (run_001, run_002, ...)."""
|
"""Find the next available run directory under base_dir (run_001, run_002, ...)."""
|
||||||
counter = 1
|
counter = 1
|
||||||
@@ -223,21 +217,37 @@ def _next_run_dir(base_dir: Path) -> Path:
|
|||||||
return run_dir
|
return run_dir
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
def render_sequence_to_pngs(seq: ObservationSequence, out_dir: Path | None = None) -> None:
|
|
||||||
"""
|
|
||||||
Render each observation to its own PNG inside a fresh run directory.
|
|
||||||
Default: PROJECT_ROOT/output/obs_png/run_XXX/observation_001.png
|
|
||||||
"""
|
|
||||||
base_dir = PNG_DIR if out_dir is None else out_dir
|
|
||||||
base_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Create a fresh run dir
|
|
||||||
run_dir = _next_run_dir(base_dir)
|
|
||||||
|
|
||||||
|
def render_sequence_to_pngs(seq: ObservationSequence, out_dir: Path) -> None:
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
run_dir = _next_run_dir(out_dir)
|
||||||
for i, obs in enumerate(seq.observations, start=1):
|
for i, obs in enumerate(seq.observations, start=1):
|
||||||
filename = run_dir / f"observation_{i:03d}.png"
|
filename = run_dir / f"observation_{i:03d}.png"
|
||||||
render_observation_to_png(seq.target_passcode, obs, filename)
|
render_observation_to_png(seq.target_passcode, obs, filename)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
obs_seq = new_observation_sequence(6, 9,4,0,0,numb_runs=50)
|
shuffle_classes = {
|
||||||
save_observation_sequence_to_json(obs_seq)
|
'RandomSplitShuffle': RandomSplitShuffleKeypad,
|
||||||
render_sequence_to_pngs(obs_seq)
|
'RandomShuffle': RandomShuffleKeypad,
|
||||||
|
'SlidingSplitShuffle': SlidingSplitShuffleKeypad,
|
||||||
|
'SlidingTowerShuffle': SlidingTowerShuffleKeypad
|
||||||
|
}
|
||||||
|
parser = argparse.ArgumentParser(description="Generate and save observation sequences with optional PNG rendering.")
|
||||||
|
parser.add_argument("--number-of-keys", type=int, default=6, help="Number of keys in the keypad (default: 6)")
|
||||||
|
parser.add_argument("--properties-per-key", type=int, default=9, help="Properties per key (default: 9)")
|
||||||
|
parser.add_argument("--passcode-length", type=int, default=4, help="Length of the passcode (default: 4)")
|
||||||
|
parser.add_argument("--complexity", type=int, default=0, help="Complexity of the passcode (default: 0)")
|
||||||
|
parser.add_argument("--disparity", type=int, default=0, help="Disparity of the passcode (default: 0)")
|
||||||
|
parser.add_argument("--num-runs", type=int, default=50, help="Number of observations to generate (default: 50)")
|
||||||
|
parser.add_argument("--shuffle-type", type=str, default="SlidingTowerShuffle", choices=list(shuffle_classes.keys()),
|
||||||
|
help="Keypad shuffle type: 'RandomShuffle' or 'SlidingTowerShuffle' (default: SlidingTowerShuffle)")
|
||||||
|
parser.add_argument("--output-dir", type=str, default="./output",
|
||||||
|
help="Custom output directory for JSON and PNG files")
|
||||||
|
args = parser.parse_args()
|
||||||
|
keypad = shuffle_classes[args.shuffle_type].new_keypad(6, 9)
|
||||||
|
obs_seq = new_observation_sequence(keypad, 4, 0, 0, numb_runs=50)
|
||||||
|
shuffle_type = str(type(keypad)).lower().split('.')[-1].replace("'>", "")
|
||||||
|
output_dir = Path(args.output_dir)
|
||||||
|
save_observation_sequence_to_json(obs_seq, output_dir / "obs.json")
|
||||||
|
render_sequence_to_pngs(obs_seq, output_dir / "obs_png")
|
||||||
544
environment.yaml
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
name: base
|
||||||
|
channels:
|
||||||
|
- defaults
|
||||||
|
dependencies:
|
||||||
|
- _anaconda_depends=2024.10=py312_openblas_0
|
||||||
|
- aiobotocore=2.12.3=py312hca03da5_0
|
||||||
|
- aiohappyeyeballs=2.4.3=py312hca03da5_0
|
||||||
|
- aiohttp=3.10.5=py312h80987f9_0
|
||||||
|
- aioitertools=0.7.1=pyhd3eb1b0_0
|
||||||
|
- aiosignal=1.2.0=pyhd3eb1b0_0
|
||||||
|
- alabaster=0.7.16=py312hca03da5_0
|
||||||
|
- altair=5.0.1=py312hca03da5_0
|
||||||
|
- anaconda-anon-usage=0.5.0=py312hd6b623d_100
|
||||||
|
- anaconda-catalogs=0.2.0=py312hca03da5_1
|
||||||
|
- anaconda-cli-base=0.4.1=py312hca03da5_1
|
||||||
|
- anaconda-client=1.13.0=py312hca03da5_0
|
||||||
|
- anaconda-cloud-auth=0.7.2=py312hca03da5_0
|
||||||
|
- anaconda-navigator=2.6.4=py312hca03da5_0
|
||||||
|
- anaconda-project=0.11.1=py312hca03da5_0
|
||||||
|
- annotated-types=0.6.0=py312hca03da5_0
|
||||||
|
- anyio=4.6.2=py312hca03da5_0
|
||||||
|
- aom=3.6.0=h313beb8_0
|
||||||
|
- appdirs=1.4.4=pyhd3eb1b0_0
|
||||||
|
- applaunchservices=0.3.0=py312hca03da5_0
|
||||||
|
- appnope=0.1.3=py312hca03da5_1001
|
||||||
|
- appscript=1.2.5=py312h80987f9_0
|
||||||
|
- archspec=0.2.3=pyhd3eb1b0_0
|
||||||
|
- argon2-cffi=21.3.0=pyhd3eb1b0_0
|
||||||
|
- argon2-cffi-bindings=21.2.0=py312h80987f9_0
|
||||||
|
- arrow=1.3.0=py312hca03da5_0
|
||||||
|
- arrow-cpp=16.1.0=hbc20fb2_0
|
||||||
|
- astroid=3.2.4=py312hca03da5_0
|
||||||
|
- astropy=6.1.3=py312h80987f9_0
|
||||||
|
- astropy-iers-data=0.2024.9.2.0.33.23=py312hca03da5_0
|
||||||
|
- asttokens=2.0.5=pyhd3eb1b0_0
|
||||||
|
- async-lru=2.0.4=py312hca03da5_0
|
||||||
|
- asyncssh=2.17.0=py312hca03da5_0
|
||||||
|
- atomicwrites=1.4.0=py_0
|
||||||
|
- attrs=24.2.0=py312hca03da5_0
|
||||||
|
- automat=20.2.0=py_0
|
||||||
|
- autopep8=2.0.4=pyhd3eb1b0_0
|
||||||
|
- aws-c-auth=0.6.19=h80987f9_0
|
||||||
|
- aws-c-cal=0.5.20=h80987f9_0
|
||||||
|
- aws-c-common=0.8.5=h80987f9_0
|
||||||
|
- aws-c-compression=0.2.16=h80987f9_0
|
||||||
|
- aws-c-event-stream=0.2.15=h313beb8_0
|
||||||
|
- aws-c-http=0.6.25=h80987f9_0
|
||||||
|
- aws-c-io=0.13.10=h80987f9_0
|
||||||
|
- aws-c-mqtt=0.7.13=h80987f9_0
|
||||||
|
- aws-c-s3=0.1.51=h80987f9_0
|
||||||
|
- aws-c-sdkutils=0.1.6=h80987f9_0
|
||||||
|
- aws-checksums=0.1.13=h80987f9_0
|
||||||
|
- aws-crt-cpp=0.18.16=h313beb8_0
|
||||||
|
- aws-sdk-cpp=1.10.55=h313beb8_0
|
||||||
|
- babel=2.11.0=py312hca03da5_0
|
||||||
|
- backports=1.1=pyhd3eb1b0_1
|
||||||
|
- backports.functools_lru_cache=1.6.4=pyhd3eb1b0_0
|
||||||
|
- backports.tempfile=1.0=pyhd3eb1b0_1
|
||||||
|
- backports.weakref=1.0.post1=py_1
|
||||||
|
- bcrypt=3.2.0=py312h80987f9_1
|
||||||
|
- beautifulsoup4=4.12.3=py312hca03da5_0
|
||||||
|
- binaryornot=0.4.4=pyhd3eb1b0_1
|
||||||
|
- black=24.8.0=py312hca03da5_0
|
||||||
|
- blas=1.0=openblas
|
||||||
|
- bleach=6.2.0=py312hca03da5_0
|
||||||
|
- blinker=1.6.2=py312hca03da5_0
|
||||||
|
- blosc=1.21.3=h313beb8_0
|
||||||
|
- bokeh=3.6.0=py312hca03da5_0
|
||||||
|
- boltons=23.0.0=py312hca03da5_0
|
||||||
|
- boost-cpp=1.82.0=h48ca7d4_2
|
||||||
|
- botocore=1.34.69=py312hca03da5_0
|
||||||
|
- bottleneck=1.4.2=py312ha86b861_0
|
||||||
|
- brotli=1.0.9=h80987f9_8
|
||||||
|
- brotli-bin=1.0.9=h80987f9_8
|
||||||
|
- brotli-python=1.0.9=py312h313beb8_8
|
||||||
|
- brunsli=0.1=hc377ac9_1
|
||||||
|
- bzip2=1.0.8=h80987f9_6
|
||||||
|
- c-ares=1.19.1=h80987f9_0
|
||||||
|
- c-blosc2=2.12.0=h7df6c2f_0
|
||||||
|
- ca-certificates=2024.11.26=hca03da5_0
|
||||||
|
- cachetools=5.3.3=py312hca03da5_0
|
||||||
|
- cctools=949.0.1=hc179dcd_25
|
||||||
|
- cctools_osx-arm64=949.0.1=h332cad3_25
|
||||||
|
- certifi=2024.8.30=py312hca03da5_0
|
||||||
|
- cffi=1.17.1=py312h3eb5a62_0
|
||||||
|
- cfitsio=3.470=h7f6438f_7
|
||||||
|
- chardet=4.0.0=py312hca03da5_1003
|
||||||
|
- charls=2.2.0=hc377ac9_0
|
||||||
|
- charset-normalizer=3.3.2=pyhd3eb1b0_0
|
||||||
|
- click=8.1.7=py312hca03da5_0
|
||||||
|
- cloudpickle=3.0.0=py312hca03da5_0
|
||||||
|
- colorama=0.4.6=py312hca03da5_0
|
||||||
|
- colorcet=3.1.0=py312hca03da5_0
|
||||||
|
- comm=0.2.1=py312hca03da5_0
|
||||||
|
- conda=24.11.0=py312hca03da5_0
|
||||||
|
- conda-build=24.11.2=py312hca03da5_0
|
||||||
|
- conda-content-trust=0.2.0=py312hca03da5_1
|
||||||
|
- conda-index=0.5.0=py312hca03da5_0
|
||||||
|
- conda-libmamba-solver=24.9.0=pyhd3eb1b0_0
|
||||||
|
- conda-pack=0.6.0=pyhd3eb1b0_0
|
||||||
|
- conda-package-handling=2.4.0=py312hca03da5_0
|
||||||
|
- conda-package-streaming=0.11.0=py312hca03da5_0
|
||||||
|
- conda-repo-cli=1.0.114=py312hca03da5_0
|
||||||
|
- conda-token=0.4.0=pyhd3eb1b0_0
|
||||||
|
- conda-verify=3.4.2=py_1
|
||||||
|
- constantly=23.10.4=py312hca03da5_0
|
||||||
|
- contourpy=1.3.1=py312h48ca7d4_0
|
||||||
|
- cookiecutter=2.6.0=py312hca03da5_0
|
||||||
|
- cryptography=43.0.3=py312h8026fc7_1
|
||||||
|
- cssselect=1.2.0=py312hca03da5_0
|
||||||
|
- curl=8.9.1=h02f6b3c_0
|
||||||
|
- cycler=0.11.0=pyhd3eb1b0_0
|
||||||
|
- cyrus-sasl=2.1.28=h9131b1a_1
|
||||||
|
- cytoolz=0.12.2=py312h80987f9_0
|
||||||
|
- dask=2024.8.2=py312hca03da5_0
|
||||||
|
- dask-core=2024.8.2=py312hca03da5_0
|
||||||
|
- dask-expr=1.1.13=py312hca03da5_0
|
||||||
|
- datasets=2.19.1=py312hca03da5_0
|
||||||
|
- datashader=0.16.3=py312hca03da5_0
|
||||||
|
- dav1d=1.2.1=h80987f9_0
|
||||||
|
- debugpy=1.6.7=py312h313beb8_0
|
||||||
|
- decorator=5.1.1=pyhd3eb1b0_0
|
||||||
|
- defusedxml=0.7.1=pyhd3eb1b0_0
|
||||||
|
- deprecated=1.2.13=py312hca03da5_0
|
||||||
|
- diff-match-patch=20200713=pyhd3eb1b0_0
|
||||||
|
- dill=0.3.8=py312hca03da5_0
|
||||||
|
- distributed=2024.8.2=py312hca03da5_0
|
||||||
|
- distro=1.9.0=py312hca03da5_0
|
||||||
|
- dmglib=0.9.5=py312hca03da5_0
|
||||||
|
- docstring-to-markdown=0.11=py312hca03da5_0
|
||||||
|
- docutils=0.18.1=py312hca03da5_3
|
||||||
|
- et_xmlfile=1.1.0=py312hca03da5_1
|
||||||
|
- executing=0.8.3=pyhd3eb1b0_0
|
||||||
|
- expat=2.6.3=h313beb8_0
|
||||||
|
- filelock=3.13.1=py312hca03da5_0
|
||||||
|
- flake8=7.1.1=py312hca03da5_0
|
||||||
|
- flask=3.0.3=py312hca03da5_0
|
||||||
|
- fmt=9.1.0=h48ca7d4_1
|
||||||
|
- fonttools=4.51.0=py312h80987f9_0
|
||||||
|
- freetype=2.12.1=h1192e45_0
|
||||||
|
- frozendict=2.4.2=py312hca03da5_0
|
||||||
|
- frozenlist=1.5.0=py312h80987f9_0
|
||||||
|
- fsspec=2024.3.1=py312hca03da5_0
|
||||||
|
- future=1.0.0=py312hca03da5_0
|
||||||
|
- gensim=4.3.3=py312hd77ebd4_0
|
||||||
|
- gettext=0.21.0=hbdbcc25_2
|
||||||
|
- gflags=2.2.2=h313beb8_1
|
||||||
|
- giflib=5.2.2=h80987f9_0
|
||||||
|
- gitdb=4.0.7=pyhd3eb1b0_0
|
||||||
|
- gitpython=3.1.43=py312hca03da5_0
|
||||||
|
- glib=2.78.4=h313beb8_0
|
||||||
|
- glib-tools=2.78.4=h313beb8_0
|
||||||
|
- glog=0.5.0=h313beb8_1
|
||||||
|
- gmp=6.2.1=hc377ac9_3
|
||||||
|
- greenlet=3.0.1=py312h313beb8_0
|
||||||
|
- gst-plugins-base=1.14.1=h313beb8_1
|
||||||
|
- gstreamer=1.14.1=h80987f9_1
|
||||||
|
- h11=0.14.0=py312hca03da5_0
|
||||||
|
- h5py=3.12.1=py312h8456320_0
|
||||||
|
- hdf5=1.12.1=h05c076b_3
|
||||||
|
- heapdict=1.0.1=pyhd3eb1b0_0
|
||||||
|
- holoviews=1.20.0=py312hca03da5_0
|
||||||
|
- httpcore=1.0.2=py312hca03da5_0
|
||||||
|
- httpx=0.27.0=py312hca03da5_0
|
||||||
|
- huggingface_hub=0.24.6=py312hca03da5_0
|
||||||
|
- hvplot=0.11.1=py312hca03da5_0
|
||||||
|
- hyperlink=21.0.0=pyhd3eb1b0_0
|
||||||
|
- icu=73.1=h313beb8_0
|
||||||
|
- idna=3.7=py312hca03da5_0
|
||||||
|
- imagecodecs=2023.1.23=py312h75b721f_1
|
||||||
|
- imageio=2.33.1=py312hca03da5_0
|
||||||
|
- imagesize=1.4.1=py312hca03da5_0
|
||||||
|
- imbalanced-learn=0.12.3=py312hca03da5_1
|
||||||
|
- importlib-metadata=8.5.0=py312hca03da5_0
|
||||||
|
- importlib_metadata=8.5.0=hd3eb1b0_0
|
||||||
|
- incremental=22.10.0=pyhd3eb1b0_0
|
||||||
|
- inflection=0.5.1=py312hca03da5_1
|
||||||
|
- iniconfig=1.1.1=pyhd3eb1b0_0
|
||||||
|
- intake=2.0.7=py312hca03da5_0
|
||||||
|
- intervaltree=3.1.0=pyhd3eb1b0_0
|
||||||
|
- ipykernel=6.29.5=py312hca03da5_0
|
||||||
|
- ipython=8.27.0=py312hca03da5_0
|
||||||
|
- ipython_genutils=0.2.0=pyhd3eb1b0_1
|
||||||
|
- ipywidgets=7.8.1=py312hca03da5_0
|
||||||
|
- isort=5.13.2=py312hca03da5_0
|
||||||
|
- itemadapter=0.3.0=pyhd3eb1b0_0
|
||||||
|
- itemloaders=1.1.0=py312hca03da5_0
|
||||||
|
- itsdangerous=2.2.0=py312hca03da5_0
|
||||||
|
- jaraco.classes=3.2.1=pyhd3eb1b0_0
|
||||||
|
- jedi=0.19.1=py312hca03da5_0
|
||||||
|
- jellyfish=1.0.1=py312h1bd1ac0_1
|
||||||
|
- jinja2=3.1.4=py312hca03da5_1
|
||||||
|
- jmespath=1.0.1=py312hca03da5_0
|
||||||
|
- joblib=1.4.2=py312hca03da5_0
|
||||||
|
- jpeg=9e=h80987f9_3
|
||||||
|
- jq=1.7.1=h80987f9_0
|
||||||
|
- json5=0.9.25=py312hca03da5_0
|
||||||
|
- jsonpatch=1.33=py312hca03da5_1
|
||||||
|
- jsonpointer=2.1=pyhd3eb1b0_0
|
||||||
|
- jsonschema=4.23.0=py312hca03da5_0
|
||||||
|
- jsonschema-specifications=2023.7.1=py312hca03da5_0
|
||||||
|
- jupyter=1.0.0=py312hca03da5_9
|
||||||
|
- jupyter-lsp=2.2.0=py312hca03da5_0
|
||||||
|
- jupyter_client=8.6.0=py312hca03da5_0
|
||||||
|
- jupyter_console=6.6.3=py312hca03da5_1
|
||||||
|
- jupyter_core=5.7.2=py312hca03da5_0
|
||||||
|
- jupyter_events=0.10.0=py312hca03da5_0
|
||||||
|
- jupyter_server=2.14.1=py312hca03da5_0
|
||||||
|
- jupyter_server_terminals=0.4.4=py312hca03da5_1
|
||||||
|
- jupyterlab=4.2.5=py312hca03da5_0
|
||||||
|
- jupyterlab-variableinspector=3.1.0=py312hca03da5_0
|
||||||
|
- jupyterlab_pygments=0.1.2=py_0
|
||||||
|
- jupyterlab_server=2.27.3=py312hca03da5_0
|
||||||
|
- jupyterlab_widgets=1.0.0=pyhd3eb1b0_1
|
||||||
|
- jxrlib=1.1=h1a28f6b_2
|
||||||
|
- keyring=24.3.1=py312hca03da5_0
|
||||||
|
- kiwisolver=1.4.4=py312h313beb8_0
|
||||||
|
- krb5=1.20.1=hf3e1bf2_1
|
||||||
|
- lazy_loader=0.4=py312hca03da5_0
|
||||||
|
- lcms2=2.12=hba8e193_0
|
||||||
|
- ld64=530=hb29bf3f_25
|
||||||
|
- ld64_osx-arm64=530=h001ce53_25
|
||||||
|
- ldid=2.1.5=h20b2a84_3
|
||||||
|
- lerc=3.0=hc377ac9_0
|
||||||
|
- libabseil=20240116.2=cxx17_h313beb8_0
|
||||||
|
- libaec=1.0.4=hc377ac9_1
|
||||||
|
- libarchive=3.7.4=h8f13d7a_0
|
||||||
|
- libavif=0.11.1=h80987f9_0
|
||||||
|
- libboost=1.82.0=h0bc93f9_2
|
||||||
|
- libbrotlicommon=1.0.9=h80987f9_8
|
||||||
|
- libbrotlidec=1.0.9=h80987f9_8
|
||||||
|
- libbrotlienc=1.0.9=h80987f9_8
|
||||||
|
- libclang=14.0.6=default_h1b80db6_1
|
||||||
|
- libclang13=14.0.6=default_h24352ff_1
|
||||||
|
- libcurl=8.9.1=h3e2b118_0
|
||||||
|
- libcxx=14.0.6=h848a8c0_0
|
||||||
|
- libdeflate=1.17=h80987f9_1
|
||||||
|
- libedit=3.1.20230828=h80987f9_0
|
||||||
|
- libev=4.33=h1a28f6b_1
|
||||||
|
- libevent=2.1.12=h02f6b3c_1
|
||||||
|
- libffi=3.4.4=hca03da5_1
|
||||||
|
- libgfortran=5.0.0=11_3_0_hca03da5_28
|
||||||
|
- libgfortran5=11.3.0=h009349e_28
|
||||||
|
- libglib=2.78.4=h0a96307_0
|
||||||
|
- libgrpc=1.62.2=h62f6fdd_0
|
||||||
|
- libiconv=1.16=h80987f9_3
|
||||||
|
- liblief=0.12.3=h313beb8_0
|
||||||
|
- libllvm14=14.0.6=h19fdd8a_4
|
||||||
|
- libmamba=1.5.11=haeffa04_0
|
||||||
|
- libmambapy=1.5.11=py312h15e39b3_0
|
||||||
|
- libnghttp2=1.57.0=h62f6fdd_0
|
||||||
|
- libopenblas=0.3.21=h269037a_0
|
||||||
|
- libpng=1.6.39=h80987f9_0
|
||||||
|
- libpq=17.0=h02f6b3c_0
|
||||||
|
- libprotobuf=4.25.3=h514c7bf_0
|
||||||
|
- libsodium=1.0.18=h1a28f6b_0
|
||||||
|
- libsolv=0.7.24=h514c7bf_1
|
||||||
|
- libspatialindex=1.9.3=hc377ac9_0
|
||||||
|
- libssh2=1.11.1=h3e2b118_0
|
||||||
|
- libthrift=0.15.0=h73c2103_2
|
||||||
|
- libtiff=4.5.1=h313beb8_0
|
||||||
|
- libwebp-base=1.3.2=h80987f9_1
|
||||||
|
- libxml2=2.13.5=h0b34f26_0
|
||||||
|
- libxslt=1.1.41=hf4d3faa_0
|
||||||
|
- libzopfli=1.0.3=hc377ac9_0
|
||||||
|
- linkify-it-py=2.0.0=py312hca03da5_0
|
||||||
|
- llvm-openmp=14.0.6=hc6e5704_0
|
||||||
|
- llvmlite=0.43.0=py312h313beb8_0
|
||||||
|
- locket=1.0.0=py312hca03da5_0
|
||||||
|
- lxml=5.3.0=py312h1d4350b_0
|
||||||
|
- lz4=4.3.2=py312h80987f9_0
|
||||||
|
- lz4-c=1.9.4=h313beb8_1
|
||||||
|
- lzo=2.10=h1a28f6b_2
|
||||||
|
- markdown=3.4.1=py312hca03da5_0
|
||||||
|
- markdown-it-py=2.2.0=py312hca03da5_1
|
||||||
|
- markupsafe=2.1.3=py312h80987f9_0
|
||||||
|
- matplotlib=3.9.2=py312hca03da5_1
|
||||||
|
- matplotlib-base=3.9.2=py312h7ef442a_1
|
||||||
|
- matplotlib-inline=0.1.6=py312hca03da5_0
|
||||||
|
- mccabe=0.7.0=pyhd3eb1b0_0
|
||||||
|
- mdit-py-plugins=0.3.0=py312hca03da5_0
|
||||||
|
- mdurl=0.1.0=py312hca03da5_0
|
||||||
|
- menuinst=2.2.0=py312hca03da5_0
|
||||||
|
- mistune=2.0.4=py312hca03da5_0
|
||||||
|
- more-itertools=10.3.0=py312hca03da5_0
|
||||||
|
- mpc=1.1.0=h8c48613_1
|
||||||
|
- mpfr=4.0.2=h695f6f0_1
|
||||||
|
- mpmath=1.3.0=py312hca03da5_0
|
||||||
|
- msgpack-python=1.0.3=py312h48ca7d4_0
|
||||||
|
- multidict=6.1.0=py312h80987f9_0
|
||||||
|
- multipledispatch=0.6.0=py312hca03da5_0
|
||||||
|
- multiprocess=0.70.15=py312hca03da5_0
|
||||||
|
- mypy=1.11.2=py312h80987f9_0
|
||||||
|
- mypy_extensions=1.0.0=py312hca03da5_0
|
||||||
|
- mysql=8.4.0=h3a6587f_1
|
||||||
|
- navigator-updater=0.5.1=py312hca03da5_0
|
||||||
|
- nbclassic=1.1.0=py312hca03da5_0
|
||||||
|
- nbclient=0.8.0=py312hca03da5_0
|
||||||
|
- nbconvert=7.16.4=py312hca03da5_0
|
||||||
|
- nbformat=5.10.4=py312hca03da5_0
|
||||||
|
- ncurses=6.4=h313beb8_0
|
||||||
|
- nest-asyncio=1.6.0=py312hca03da5_0
|
||||||
|
- networkx=3.3=py312hca03da5_0
|
||||||
|
- nltk=3.9.1=py312hca03da5_0
|
||||||
|
- notebook=7.2.2=py312hca03da5_1
|
||||||
|
- notebook-shim=0.2.3=py312hca03da5_0
|
||||||
|
- numba=0.60.0=py312hd77ebd4_0
|
||||||
|
- numexpr=2.10.1=py312h5d9532f_0
|
||||||
|
- numpy=1.26.4=py312h7f4fdc5_0
|
||||||
|
- numpy-base=1.26.4=py312he047099_0
|
||||||
|
- numpydoc=1.7.0=py312hca03da5_0
|
||||||
|
- oniguruma=6.9.7.1=h1a28f6b_0
|
||||||
|
- openjpeg=2.5.2=h54b8e55_0
|
||||||
|
- openldap=2.6.4=he7ef289_0
|
||||||
|
- openpyxl=3.1.5=py312h80987f9_0
|
||||||
|
- openssl=3.0.15=h80987f9_0
|
||||||
|
- orc=2.0.1=h937ddfc_0
|
||||||
|
- overrides=7.4.0=py312hca03da5_0
|
||||||
|
- packaging=24.1=py312hca03da5_0
|
||||||
|
- pandas=2.2.3=py312hcf29cfe_0
|
||||||
|
- pandocfilters=1.5.0=pyhd3eb1b0_0
|
||||||
|
- panel=1.5.3=py312hca03da5_0
|
||||||
|
- param=2.1.1=py312hca03da5_0
|
||||||
|
- parsel=1.8.1=py312hca03da5_0
|
||||||
|
- parso=0.8.3=pyhd3eb1b0_0
|
||||||
|
- partd=1.4.1=py312hca03da5_0
|
||||||
|
- patch=2.7.6=h1a28f6b_1001
|
||||||
|
- pathspec=0.10.3=py312hca03da5_0
|
||||||
|
- patsy=0.5.6=py312hca03da5_0
|
||||||
|
- pcre2=10.42=hb066dcc_1
|
||||||
|
- pexpect=4.8.0=pyhd3eb1b0_3
|
||||||
|
- pickleshare=0.7.5=pyhd3eb1b0_1003
|
||||||
|
- pillow=11.0.0=py312hfaf4e14_0
|
||||||
|
- pip=24.2=py312hca03da5_0
|
||||||
|
- pkce=1.0.3=py312hca03da5_0
|
||||||
|
- pkginfo=1.11.2=py312hca03da5_0
|
||||||
|
- platformdirs=3.10.0=py312hca03da5_0
|
||||||
|
- plotly=5.24.1=py312h989b03a_0
|
||||||
|
- pluggy=1.5.0=py312hca03da5_0
|
||||||
|
- ply=3.11=py312hca03da5_1
|
||||||
|
- prometheus_client=0.21.0=py312hca03da5_0
|
||||||
|
- prompt-toolkit=3.0.43=py312hca03da5_0
|
||||||
|
- prompt_toolkit=3.0.43=hd3eb1b0_0
|
||||||
|
- propcache=0.2.0=py312h80987f9_0
|
||||||
|
- protego=0.1.16=py_0
|
||||||
|
- protobuf=4.25.3=py312h8472c4a_0
|
||||||
|
- psutil=5.9.0=py312h80987f9_0
|
||||||
|
- ptyprocess=0.7.0=pyhd3eb1b0_2
|
||||||
|
- pure_eval=0.2.2=pyhd3eb1b0_0
|
||||||
|
- py-cpuinfo=9.0.0=py312hca03da5_0
|
||||||
|
- py-lief=0.12.3=py312h313beb8_0
|
||||||
|
- pyarrow=16.1.0=py312hd77ebd4_0
|
||||||
|
- pyasn1=0.4.8=pyhd3eb1b0_0
|
||||||
|
- pyasn1-modules=0.2.8=py_0
|
||||||
|
- pybind11-abi=5=hd3eb1b0_0
|
||||||
|
- pycodestyle=2.12.1=py312hca03da5_0
|
||||||
|
- pycosat=0.6.6=py312h80987f9_1
|
||||||
|
- pycparser=2.21=pyhd3eb1b0_0
|
||||||
|
- pyct=0.5.0=py312hca03da5_0
|
||||||
|
- pycurl=7.45.3=py312h02f6b3c_0
|
||||||
|
- pydantic=2.8.2=py312hca03da5_0
|
||||||
|
- pydantic-core=2.20.1=py312hf0e4da2_0
|
||||||
|
- pydantic-settings=2.6.1=py312hca03da5_0
|
||||||
|
- pydeck=0.8.0=py312hca03da5_2
|
||||||
|
- pydispatcher=2.0.5=py312hca03da5_3
|
||||||
|
- pydocstyle=6.3.0=py312hca03da5_0
|
||||||
|
- pyerfa=2.0.1.4=py312ha86b861_0
|
||||||
|
- pyflakes=3.2.0=py312hca03da5_0
|
||||||
|
- pygithub=2.4.0=py312hca03da5_0
|
||||||
|
- pygments=2.15.1=py312hca03da5_1
|
||||||
|
- pyjwt=2.9.0=py312hca03da5_0
|
||||||
|
- pylint=3.2.7=py312hca03da5_0
|
||||||
|
- pylint-venv=3.0.3=py312hca03da5_0
|
||||||
|
- pyls-spyder=0.4.0=pyhd3eb1b0_0
|
||||||
|
- pynacl=1.5.0=py312h80987f9_0
|
||||||
|
- pyobjc-core=10.1=py312h80987f9_0
|
||||||
|
- pyobjc-framework-cocoa=10.1=py312hb094c41_0
|
||||||
|
- pyobjc-framework-coreservices=10.1=py312hdd8dd1f_0
|
||||||
|
- pyobjc-framework-fsevents=10.1=py312hca03da5_0
|
||||||
|
- pyodbc=5.1.0=py312h313beb8_0
|
||||||
|
- pyopenssl=24.2.1=py312hca03da5_0
|
||||||
|
- pyparsing=3.2.0=py312hca03da5_0
|
||||||
|
- pyqt=5.15.10=py312h313beb8_0
|
||||||
|
- pyqt5-sip=12.13.0=py312h80987f9_0
|
||||||
|
- pyqtwebengine=5.15.10=py312h313beb8_0
|
||||||
|
- pysocks=1.7.1=py312hca03da5_0
|
||||||
|
- pytables=3.10.1=py312h905a39b_0
|
||||||
|
- pytest=7.4.4=py312hca03da5_0
|
||||||
|
- python=3.12.7=h99e199e_0
|
||||||
|
- python-dateutil=2.9.0post0=py312hca03da5_2
|
||||||
|
- python-dotenv=0.21.0=py312hca03da5_0
|
||||||
|
- python-fastjsonschema=2.20.0=py312hca03da5_0
|
||||||
|
- python-json-logger=2.0.7=py312hca03da5_0
|
||||||
|
- python-libarchive-c=5.1=pyhd3eb1b0_0
|
||||||
|
- python-lmdb=1.4.1=py312h313beb8_0
|
||||||
|
- python-lsp-black=2.0.0=py312hca03da5_0
|
||||||
|
- python-lsp-jsonrpc=1.1.2=pyhd3eb1b0_0
|
||||||
|
- python-lsp-server=1.12.0=py312h989b03a_0
|
||||||
|
- python-slugify=5.0.2=pyhd3eb1b0_0
|
||||||
|
- python-tzdata=2023.3=pyhd3eb1b0_0
|
||||||
|
- python-xxhash=2.0.2=py312h80987f9_1
|
||||||
|
- python.app=3=py312h80987f9_1
|
||||||
|
- pytoolconfig=1.2.6=py312hca03da5_0
|
||||||
|
- pytz=2024.1=py312hca03da5_0
|
||||||
|
- pyuca=1.2=py312hca03da5_1
|
||||||
|
- pyviz_comms=3.0.2=py312hca03da5_0
|
||||||
|
- pywavelets=1.7.0=py312h80987f9_0
|
||||||
|
- pyyaml=6.0.2=py312h80987f9_0
|
||||||
|
- pyzmq=25.1.2=py312h313beb8_0
|
||||||
|
- qdarkstyle=3.2.3=pyhd3eb1b0_0
|
||||||
|
- qstylizer=0.2.2=py312hca03da5_0
|
||||||
|
- qt-main=5.15.2=h0917680_11
|
||||||
|
- qt-webengine=5.15.9=h2903aaf_7
|
||||||
|
- qtawesome=1.3.1=py312hca03da5_0
|
||||||
|
- qtconsole=5.6.0=py312hca03da5_0
|
||||||
|
- qtpy=2.4.1=py312hca03da5_0
|
||||||
|
- queuelib=1.6.2=py312hca03da5_0
|
||||||
|
- re2=2022.04.01=hc377ac9_0
|
||||||
|
- readchar=4.0.5=py312hca03da5_0
|
||||||
|
- readline=8.2=h1a28f6b_0
|
||||||
|
- referencing=0.30.2=py312hca03da5_0
|
||||||
|
- regex=2024.9.11=py312h80987f9_0
|
||||||
|
- reproc=14.2.4=h313beb8_2
|
||||||
|
- reproc-cpp=14.2.4=h313beb8_2
|
||||||
|
- requests=2.32.3=py312hca03da5_1
|
||||||
|
- requests-file=1.5.1=pyhd3eb1b0_0
|
||||||
|
- requests-toolbelt=1.0.0=py312hca03da5_0
|
||||||
|
- responses=0.13.3=pyhd3eb1b0_0
|
||||||
|
- rfc3339-validator=0.1.4=py312hca03da5_0
|
||||||
|
- rfc3986-validator=0.1.1=py312hca03da5_0
|
||||||
|
- rich=13.9.4=py312hca03da5_0
|
||||||
|
- rope=1.12.0=py312hca03da5_0
|
||||||
|
- rpds-py=0.10.6=py312h2aea54e_1
|
||||||
|
- rtree=1.0.1=py312hca03da5_0
|
||||||
|
- ruamel.yaml=0.18.6=py312h80987f9_0
|
||||||
|
- ruamel.yaml.clib=0.2.8=py312h80987f9_0
|
||||||
|
- ruamel_yaml=0.17.21=py312h80987f9_0
|
||||||
|
- s3fs=2024.3.1=py312hca03da5_0
|
||||||
|
- safetensors=0.4.5=py312h7805bc0_1
|
||||||
|
- scikit-image=0.24.0=py312hd77ebd4_0
|
||||||
|
- scikit-learn=1.5.1=py312hd77ebd4_0
|
||||||
|
- scipy=1.13.1=py312ha409365_0
|
||||||
|
- scrapy=2.12.0=py312hca03da5_0
|
||||||
|
- seaborn=0.13.2=py312hca03da5_0
|
||||||
|
- semver=3.0.2=py312hca03da5_0
|
||||||
|
- send2trash=1.8.2=py312hca03da5_0
|
||||||
|
- service_identity=18.1.0=pyhd3eb1b0_1
|
||||||
|
- setuptools=75.1.0=py312hca03da5_0
|
||||||
|
- shellingham=1.5.0=py312hca03da5_0
|
||||||
|
- sip=6.7.12=py312h313beb8_0
|
||||||
|
- six=1.16.0=pyhd3eb1b0_1
|
||||||
|
- smart_open=5.2.1=py312hca03da5_0
|
||||||
|
- smmap=4.0.0=pyhd3eb1b0_0
|
||||||
|
- snappy=1.2.1=h313beb8_0
|
||||||
|
- sniffio=1.3.0=py312hca03da5_0
|
||||||
|
- snowballstemmer=2.2.0=pyhd3eb1b0_0
|
||||||
|
- sortedcontainers=2.4.0=pyhd3eb1b0_0
|
||||||
|
- soupsieve=2.5=py312hca03da5_0
|
||||||
|
- sphinx=7.3.7=py312hca03da5_0
|
||||||
|
- sphinxcontrib-applehelp=1.0.2=pyhd3eb1b0_0
|
||||||
|
- sphinxcontrib-devhelp=1.0.2=pyhd3eb1b0_0
|
||||||
|
- sphinxcontrib-htmlhelp=2.0.0=pyhd3eb1b0_0
|
||||||
|
- sphinxcontrib-jsmath=1.0.1=pyhd3eb1b0_0
|
||||||
|
- sphinxcontrib-qthelp=1.0.3=pyhd3eb1b0_0
|
||||||
|
- sphinxcontrib-serializinghtml=1.1.10=py312hca03da5_0
|
||||||
|
- spyder=6.0.1=py312hca03da5_0
|
||||||
|
- spyder-kernels=3.0.0=py312h989b03a_0
|
||||||
|
- sqlalchemy=2.0.34=py312hbe2cdee_0
|
||||||
|
- sqlite=3.45.3=h80987f9_0
|
||||||
|
- stack_data=0.2.0=pyhd3eb1b0_0
|
||||||
|
- statsmodels=0.14.2=py312ha86b861_0
|
||||||
|
- streamlit=1.40.1=py312hca03da5_0
|
||||||
|
- superqt=0.6.7=py312h989b03a_0
|
||||||
|
- sympy=1.13.2=py312hca03da5_0
|
||||||
|
- tabulate=0.9.0=py312hca03da5_0
|
||||||
|
- tapi=1100.0.11=h8754e6a_1
|
||||||
|
- tbb=2021.8.0=h48ca7d4_0
|
||||||
|
- tblib=1.7.0=pyhd3eb1b0_0
|
||||||
|
- tenacity=9.0.0=py312hca03da5_0
|
||||||
|
- terminado=0.17.1=py312hca03da5_0
|
||||||
|
- text-unidecode=1.3=pyhd3eb1b0_0
|
||||||
|
- textdistance=4.6.3=py312h989b03a_0
|
||||||
|
- threadpoolctl=3.5.0=py312h989b03a_0
|
||||||
|
- three-merge=0.1.1=pyhd3eb1b0_0
|
||||||
|
- tifffile=2023.4.12=py312hca03da5_0
|
||||||
|
- tinycss2=1.2.1=py312hca03da5_0
|
||||||
|
- tk=8.6.14=h6ba3021_0
|
||||||
|
- tldextract=5.1.2=py312hca03da5_0
|
||||||
|
- tokenizers=0.20.1=py312he2d9c3e_1
|
||||||
|
- toml=0.10.2=pyhd3eb1b0_0
|
||||||
|
- tomli=2.0.1=py312hca03da5_1
|
||||||
|
- tomlkit=0.13.2=py312hca03da5_0
|
||||||
|
- toolz=0.12.0=py312hca03da5_0
|
||||||
|
- tornado=6.4.1=py312h80987f9_0
|
||||||
|
- tqdm=4.66.5=py312h989b03a_0
|
||||||
|
- traitlets=5.14.3=py312hca03da5_0
|
||||||
|
- transformers=4.45.2=py312hca03da5_0
|
||||||
|
- truststore=0.8.0=py312hca03da5_0
|
||||||
|
- twisted=23.10.0=py312hca03da5_0
|
||||||
|
- typer=0.9.0=py312hca03da5_0
|
||||||
|
- typing-extensions=4.11.0=py312hca03da5_0
|
||||||
|
- typing_extensions=4.11.0=py312hca03da5_0
|
||||||
|
- tzdata=2024b=h04d1e81_0
|
||||||
|
- uc-micro-py=1.0.1=py312hca03da5_0
|
||||||
|
- ujson=5.10.0=py312h313beb8_0
|
||||||
|
- unicodedata2=15.1.0=py312h80987f9_0
|
||||||
|
- unidecode=1.3.8=py312hca03da5_0
|
||||||
|
- unixodbc=2.3.11=h1a28f6b_0
|
||||||
|
- urllib3=2.2.3=py312hca03da5_0
|
||||||
|
- utf8proc=2.6.1=h80987f9_1
|
||||||
|
- w3lib=2.1.2=py312hca03da5_0
|
||||||
|
- watchdog=4.0.1=py312h80987f9_0
|
||||||
|
- wcwidth=0.2.5=pyhd3eb1b0_0
|
||||||
|
- webencodings=0.5.1=py312hca03da5_2
|
||||||
|
- websocket-client=1.8.0=py312hca03da5_0
|
||||||
|
- werkzeug=3.0.6=py312hca03da5_0
|
||||||
|
- whatthepatch=1.0.2=py312hca03da5_0
|
||||||
|
- wheel=0.44.0=py312hca03da5_0
|
||||||
|
- widgetsnbextension=3.6.6=py312hca03da5_0
|
||||||
|
- wrapt=1.14.1=py312h80987f9_0
|
||||||
|
- wurlitzer=3.0.2=py312hca03da5_0
|
||||||
|
- xarray=2023.6.0=py312hca03da5_0
|
||||||
|
- xlwings=0.32.1=py312hca03da5_0
|
||||||
|
- xxhash=0.8.0=h1a28f6b_3
|
||||||
|
- xyzservices=2022.9.0=py312hca03da5_1
|
||||||
|
- xz=5.4.6=h80987f9_1
|
||||||
|
- yaml=0.2.5=h1a28f6b_0
|
||||||
|
- yaml-cpp=0.8.0=h313beb8_1
|
||||||
|
- yapf=0.40.2=py312hca03da5_0
|
||||||
|
- yarl=1.18.0=py312h80987f9_0
|
||||||
|
- zeromq=4.3.5=h313beb8_0
|
||||||
|
- zfp=1.0.0=h313beb8_0
|
||||||
|
- zict=3.0.0=py312hca03da5_0
|
||||||
|
- zipp=3.21.0=py312hca03da5_0
|
||||||
|
- zlib=1.2.13=h18a0788_1
|
||||||
|
- zlib-ng=2.0.7=h80987f9_0
|
||||||
|
- zope=1.0=py312hca03da5_1
|
||||||
|
- zope.interface=7.1.1=py312h80987f9_0
|
||||||
|
- zstandard=0.23.0=py312h1a4646a_1
|
||||||
|
- zstd=1.5.6=hfb09047_0
|
||||||
|
- pip:
|
||||||
|
- svgpathtools==1.7.0
|
||||||
|
- svgwrite==1.4.3
|
||||||
|
prefix: /opt/homebrew/anaconda3
|
||||||
125
environment.yml
@@ -1,125 +0,0 @@
|
|||||||
name: evilnkode
|
|
||||||
channels:
|
|
||||||
- defaults
|
|
||||||
dependencies:
|
|
||||||
- anyio=4.6.2=py312hca03da5_0
|
|
||||||
- appnope=0.1.3=py312hca03da5_1001
|
|
||||||
- argon2-cffi=21.3.0=pyhd3eb1b0_0
|
|
||||||
- argon2-cffi-bindings=21.2.0=py312h80987f9_0
|
|
||||||
- asttokens=2.0.5=pyhd3eb1b0_0
|
|
||||||
- async-lru=2.0.4=py312hca03da5_0
|
|
||||||
- attrs=24.2.0=py312hca03da5_0
|
|
||||||
- babel=2.11.0=py312hca03da5_0
|
|
||||||
- beautifulsoup4=4.12.3=py312hca03da5_0
|
|
||||||
- bleach=6.2.0=py312hca03da5_0
|
|
||||||
- brotli-python=1.0.9=py312h313beb8_8
|
|
||||||
- bzip2=1.0.8=h80987f9_6
|
|
||||||
- ca-certificates=2024.12.31=hca03da5_0
|
|
||||||
- certifi=2024.12.14=py312hca03da5_0
|
|
||||||
- cffi=1.17.1=py312h3eb5a62_0
|
|
||||||
- charset-normalizer=3.3.2=pyhd3eb1b0_0
|
|
||||||
- comm=0.2.1=py312hca03da5_0
|
|
||||||
- debugpy=1.6.7=py312h313beb8_0
|
|
||||||
- decorator=5.1.1=pyhd3eb1b0_0
|
|
||||||
- defusedxml=0.7.1=pyhd3eb1b0_0
|
|
||||||
- executing=0.8.3=pyhd3eb1b0_0
|
|
||||||
- expat=2.6.3=h313beb8_0
|
|
||||||
- h11=0.14.0=py312hca03da5_0
|
|
||||||
- httpcore=1.0.2=py312hca03da5_0
|
|
||||||
- httpx=0.27.0=py312hca03da5_0
|
|
||||||
- idna=3.7=py312hca03da5_0
|
|
||||||
- ipykernel=6.29.5=py312hca03da5_0
|
|
||||||
- ipython=8.27.0=py312hca03da5_0
|
|
||||||
- jedi=0.19.1=py312hca03da5_0
|
|
||||||
- jinja2=3.1.4=py312hca03da5_1
|
|
||||||
- json5=0.9.25=py312hca03da5_0
|
|
||||||
- jsonschema=4.23.0=py312hca03da5_0
|
|
||||||
- jsonschema-specifications=2023.7.1=py312hca03da5_0
|
|
||||||
- jupyter-lsp=2.2.0=py312hca03da5_0
|
|
||||||
- jupyter_client=8.6.0=py312hca03da5_0
|
|
||||||
- jupyter_core=5.7.2=py312hca03da5_0
|
|
||||||
- jupyter_events=0.10.0=py312hca03da5_0
|
|
||||||
- jupyter_server=2.14.1=py312hca03da5_0
|
|
||||||
- jupyter_server_terminals=0.4.4=py312hca03da5_1
|
|
||||||
- jupyterlab=4.2.5=py312hca03da5_0
|
|
||||||
- jupyterlab_pygments=0.1.2=py_0
|
|
||||||
- jupyterlab_server=2.27.3=py312hca03da5_0
|
|
||||||
- libcxx=14.0.6=h848a8c0_0
|
|
||||||
- libffi=3.4.4=hca03da5_1
|
|
||||||
- libsodium=1.0.18=h1a28f6b_0
|
|
||||||
- markupsafe=2.1.3=py312h80987f9_0
|
|
||||||
- matplotlib-inline=0.1.6=py312hca03da5_0
|
|
||||||
- mistune=2.0.4=py312hca03da5_0
|
|
||||||
- nbclient=0.8.0=py312hca03da5_0
|
|
||||||
- nbconvert=7.16.4=py312hca03da5_0
|
|
||||||
- nbformat=5.10.4=py312hca03da5_0
|
|
||||||
- ncurses=6.4=h313beb8_0
|
|
||||||
- nest-asyncio=1.6.0=py312hca03da5_0
|
|
||||||
- notebook=7.2.2=py312hca03da5_1
|
|
||||||
- notebook-shim=0.2.3=py312hca03da5_0
|
|
||||||
- openssl=3.0.15=h80987f9_0
|
|
||||||
- overrides=7.4.0=py312hca03da5_0
|
|
||||||
- packaging=24.1=py312hca03da5_0
|
|
||||||
- pandocfilters=1.5.0=pyhd3eb1b0_0
|
|
||||||
- parso=0.8.3=pyhd3eb1b0_0
|
|
||||||
- pexpect=4.8.0=pyhd3eb1b0_3
|
|
||||||
- pip=24.2=py312hca03da5_0
|
|
||||||
- platformdirs=3.10.0=py312hca03da5_0
|
|
||||||
- prometheus_client=0.21.0=py312hca03da5_0
|
|
||||||
- prompt-toolkit=3.0.43=py312hca03da5_0
|
|
||||||
- prompt_toolkit=3.0.43=hd3eb1b0_0
|
|
||||||
- psutil=5.9.0=py312h80987f9_0
|
|
||||||
- ptyprocess=0.7.0=pyhd3eb1b0_2
|
|
||||||
- pure_eval=0.2.2=pyhd3eb1b0_0
|
|
||||||
- pycparser=2.21=pyhd3eb1b0_0
|
|
||||||
- pygments=2.15.1=py312hca03da5_1
|
|
||||||
- pysocks=1.7.1=py312hca03da5_0
|
|
||||||
- python=3.12.7=h99e199e_0
|
|
||||||
- python-dateutil=2.9.0post0=py312hca03da5_2
|
|
||||||
- python-fastjsonschema=2.20.0=py312hca03da5_0
|
|
||||||
- python-json-logger=2.0.7=py312hca03da5_0
|
|
||||||
- pytz=2024.1=py312hca03da5_0
|
|
||||||
- pyyaml=6.0.2=py312h80987f9_0
|
|
||||||
- pyzmq=25.1.2=py312h313beb8_0
|
|
||||||
- readline=8.2=h1a28f6b_0
|
|
||||||
- referencing=0.30.2=py312hca03da5_0
|
|
||||||
- requests=2.32.3=py312hca03da5_1
|
|
||||||
- rfc3339-validator=0.1.4=py312hca03da5_0
|
|
||||||
- rfc3986-validator=0.1.1=py312hca03da5_0
|
|
||||||
- rpds-py=0.10.6=py312h2aea54e_1
|
|
||||||
- send2trash=1.8.2=py312hca03da5_0
|
|
||||||
- setuptools=75.1.0=py312hca03da5_0
|
|
||||||
- six=1.16.0=pyhd3eb1b0_1
|
|
||||||
- sniffio=1.3.0=py312hca03da5_0
|
|
||||||
- soupsieve=2.5=py312hca03da5_0
|
|
||||||
- sqlite=3.45.3=h80987f9_0
|
|
||||||
- stack_data=0.2.0=pyhd3eb1b0_0
|
|
||||||
- terminado=0.17.1=py312hca03da5_0
|
|
||||||
- tinycss2=1.2.1=py312hca03da5_0
|
|
||||||
- tk=8.6.14=h6ba3021_0
|
|
||||||
- tornado=6.4.1=py312h80987f9_0
|
|
||||||
- traitlets=5.14.3=py312hca03da5_0
|
|
||||||
- typing-extensions=4.11.0=py312hca03da5_0
|
|
||||||
- typing_extensions=4.11.0=py312hca03da5_0
|
|
||||||
- tzdata=2024b=h04d1e81_0
|
|
||||||
- urllib3=2.2.3=py312hca03da5_0
|
|
||||||
- wcwidth=0.2.5=pyhd3eb1b0_0
|
|
||||||
- webencodings=0.5.1=py312hca03da5_2
|
|
||||||
- websocket-client=1.8.0=py312hca03da5_0
|
|
||||||
- wheel=0.44.0=py312hca03da5_0
|
|
||||||
- xz=5.4.6=h80987f9_1
|
|
||||||
- yaml=0.2.5=h1a28f6b_0
|
|
||||||
- zeromq=4.3.5=h313beb8_0
|
|
||||||
- zlib=1.2.13=h18a0788_1
|
|
||||||
- pip:
|
|
||||||
- contourpy==1.3.1
|
|
||||||
- cycler==0.12.1
|
|
||||||
- fonttools==4.55.3
|
|
||||||
- iniconfig==2.0.0
|
|
||||||
- kiwisolver==1.4.7
|
|
||||||
- matplotlib==3.9.3
|
|
||||||
- numpy==2.1.3
|
|
||||||
- pillow==11.0.0
|
|
||||||
- pluggy==1.5.0
|
|
||||||
- pyparsing==3.2.0
|
|
||||||
- pytest==8.3.4
|
|
||||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
199
notebooks/evilnkode.ipynb
Normal file
109
src/benchmark.py
@@ -1,19 +1,19 @@
|
|||||||
from src.evilkode import Evilkode
|
from tqdm import tqdm
|
||||||
|
from src.evilnkode import EvilNKode
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from statistics import mean, variance
|
from src.utils import observations, passcode_generator
|
||||||
|
from src.keypad.keypad import BaseKeypad
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import pickle
|
||||||
from src.utils import ShuffleTypes, observations, passcode_generator
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Benchmark:
|
class Benchmark:
|
||||||
mean: int
|
iterations_to_break: list[int]
|
||||||
variance: int
|
iterations_to_replay: list[int]
|
||||||
runs: list[int]
|
|
||||||
|
|
||||||
|
|
||||||
def shuffle_benchmark(
|
def benchmark(
|
||||||
number_of_keys: int,
|
number_of_keys: int,
|
||||||
properties_per_key: int,
|
properties_per_key: int,
|
||||||
passcode_len: int,
|
passcode_len: int,
|
||||||
@@ -21,85 +21,44 @@ def shuffle_benchmark(
|
|||||||
run_count: int,
|
run_count: int,
|
||||||
complexity: int,
|
complexity: int,
|
||||||
disparity: int,
|
disparity: int,
|
||||||
shuffle_type: ShuffleTypes,
|
keypad: BaseKeypad,
|
||||||
file_path: str = '../output',
|
file_path: Path = '../output',
|
||||||
overwrite: bool = False
|
overwrite: bool = False
|
||||||
) -> Benchmark:
|
) -> Benchmark:
|
||||||
file_name = f"{shuffle_type.name.lower()}-{number_of_keys}-{properties_per_key}-{passcode_len}-{max_tries_before_lockout}-{complexity}-{disparity}-{run_count}.txt"
|
shuffle_type = str(type(keypad)).lower().split('.')[-1].replace("'>", "")
|
||||||
full_path = Path(file_path) / file_name
|
file_name = f"{shuffle_type}-{number_of_keys}-{properties_per_key}-{passcode_len}-{max_tries_before_lockout}-{complexity}-{disparity}-{run_count}.pkl"
|
||||||
|
full_path = Path(file_path) / "benchmark" / file_name
|
||||||
if not overwrite and full_path.exists():
|
if not overwrite and full_path.exists():
|
||||||
print(f"file exists {file_path}")
|
print(f"File exists: {full_path}")
|
||||||
with open(full_path, "r") as fp:
|
with open(full_path, "rb") as fp:
|
||||||
runs = fp.readline()
|
return pickle.load(fp)
|
||||||
runs = runs.split(',')
|
|
||||||
runs = [int(i) for i in runs]
|
iterations_to_break = []
|
||||||
return Benchmark(
|
iterations_to_replay = []
|
||||||
mean=mean(runs),
|
for _ in tqdm(range(run_count)):
|
||||||
variance=variance(runs),
|
|
||||||
runs=runs
|
|
||||||
)
|
|
||||||
runs = []
|
|
||||||
for _ in range(run_count):
|
|
||||||
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
|
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
|
||||||
evilkode = Evilkode(
|
evilnkode = EvilNKode(
|
||||||
observations=observations(
|
observations=observations(
|
||||||
target_passcode=passcode,
|
target_passcode=passcode,
|
||||||
number_of_keys=number_of_keys,
|
keypad=keypad,
|
||||||
properties_per_key=properties_per_key,
|
|
||||||
min_complexity=complexity,
|
|
||||||
min_disparity=disparity,
|
|
||||||
shuffle_type=shuffle_type,
|
|
||||||
),
|
),
|
||||||
number_of_keys=number_of_keys,
|
number_of_keys=number_of_keys,
|
||||||
properties_per_key=properties_per_key,
|
properties_per_key=properties_per_key,
|
||||||
passcode_len=passcode_len,
|
passcode_len=passcode_len,
|
||||||
max_tries_before_lockout=max_tries_before_lockout,
|
max_tries_before_lockout=max_tries_before_lockout,
|
||||||
)
|
)
|
||||||
evilout = evilkode.run()
|
evilout = evilnkode.run()
|
||||||
runs.append(evilout.iterations)
|
iterations_to_break.append(evilout.iterations_to_break)
|
||||||
|
iterations_to_replay.append(evilout.iterations_to_replay)
|
||||||
|
|
||||||
|
benchmark_result = Benchmark(
|
||||||
|
iterations_to_break=iterations_to_break,
|
||||||
|
iterations_to_replay=iterations_to_replay
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_path:
|
||||||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(full_path, "w") as fp:
|
with open(full_path, "wb") as fp:
|
||||||
fp.write(",".join([str(i) for i in runs])),
|
pickle.dump(benchmark_result, fp)
|
||||||
|
|
||||||
return Benchmark(
|
return benchmark_result
|
||||||
mean=mean(runs),
|
|
||||||
variance=variance(runs),
|
|
||||||
runs=runs
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def full_shuffle_benchmark(
|
|
||||||
number_of_keys: int,
|
|
||||||
properties_per_key: int,
|
|
||||||
passcode_len: int,
|
|
||||||
max_tries_before_lockout: int,
|
|
||||||
run_count: int,
|
|
||||||
complexity: int,
|
|
||||||
disparity: int,
|
|
||||||
) -> Benchmark:
|
|
||||||
runs = []
|
|
||||||
for _ in range(run_count):
|
|
||||||
passcode = passcode_generator(number_of_keys, properties_per_key, passcode_len, complexity, disparity)
|
|
||||||
evilkode = Evilkode(
|
|
||||||
observations=observations(
|
|
||||||
target_passcode=passcode,
|
|
||||||
number_of_keys=number_of_keys,
|
|
||||||
properties_per_key=properties_per_key,
|
|
||||||
min_complexity=complexity,
|
|
||||||
min_disparity=disparity,
|
|
||||||
shuffle_type=ShuffleTypes.FULL_SHUFFLE,
|
|
||||||
),
|
|
||||||
number_of_keys=number_of_keys,
|
|
||||||
properties_per_key=properties_per_key,
|
|
||||||
passcode_len=passcode_len,
|
|
||||||
max_tries_before_lockout=max_tries_before_lockout,
|
|
||||||
)
|
|
||||||
evilout = evilkode.run()
|
|
||||||
runs.append(evilout.iterations)
|
|
||||||
|
|
||||||
return Benchmark(
|
|
||||||
mean=mean(runs),
|
|
||||||
variance=variance(runs),
|
|
||||||
runs=runs
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ class Observation:
|
|||||||
def property_list(self) -> list[set[int]]:
|
def property_list(self) -> list[set[int]]:
|
||||||
return [set(self.keypad[idx]) for idx in self.key_selection]
|
return [set(self.keypad[idx]) for idx in self.key_selection]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flat_keypad(self) -> list[int]:
|
||||||
|
return [num for row in self.keypad for num in row]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EvilOutput:
|
class EvilOutput:
|
||||||
possible_nkodes: list[list[int]]
|
iterations_to_break: int
|
||||||
iterations: int
|
iterations_to_replay: int
|
||||||
|
|
||||||
@property
|
|
||||||
def number_of_possible_nkode(self):
|
|
||||||
return math.prod([len(el) for el in self.possible_nkodes])
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Evilkode:
|
class EvilNKode:
|
||||||
observations: Iterator[Observation]
|
observations: Iterator[Observation]
|
||||||
passcode_len: int
|
passcode_len: int
|
||||||
number_of_keys: int
|
number_of_keys: int
|
||||||
@@ -32,16 +32,31 @@ class Evilkode:
|
|||||||
max_tries_before_lockout: int = 5
|
max_tries_before_lockout: int = 5
|
||||||
possible_nkode = None
|
possible_nkode = None
|
||||||
|
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
possible_values = set(range(self.number_of_keys * self.properties_per_key))
|
possible_values = set(range(self.number_of_keys * self.properties_per_key))
|
||||||
self.possible_nkode = [possible_values.copy() for _ in range(self.passcode_len)]
|
self.possible_nkode = [possible_values.copy() for _ in range(self.passcode_len)]
|
||||||
|
|
||||||
def run(self) -> EvilOutput:
|
def run(self) -> EvilOutput:
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
iterations_to_replay = None
|
||||||
for idx, obs in enumerate(self.observations):
|
for idx, obs in enumerate(self.observations):
|
||||||
|
if iterations_to_replay is None:
|
||||||
|
replay_possibilities = self.replay_attack(obs)
|
||||||
|
if replay_possibilities <= self.max_tries_before_lockout:
|
||||||
|
iterations_to_replay = idx + 1
|
||||||
if math.prod([len(el) for el in self.possible_nkode]) <= self.max_tries_before_lockout:
|
if math.prod([len(el) for el in self.possible_nkode]) <= self.max_tries_before_lockout:
|
||||||
return EvilOutput(possible_nkodes=[list(el) for el in self.possible_nkode], iterations=idx+1)
|
assert iterations_to_replay <= idx + 1
|
||||||
|
return EvilOutput(
|
||||||
|
# possible_nkodes=[list(el) for el in self.possible_nkode],
|
||||||
|
iterations_to_break=idx + 1,
|
||||||
|
iterations_to_replay=iterations_to_replay
|
||||||
|
)
|
||||||
for jdx, props in enumerate(obs.property_list):
|
for jdx, props in enumerate(obs.property_list):
|
||||||
self.possible_nkode[jdx] = props.intersection(self.possible_nkode[jdx])
|
self.possible_nkode[jdx] = props.intersection(self.possible_nkode[jdx])
|
||||||
raise Exception("error in Evilkode, observations stopped yielding")
|
raise Exception("error in Evilkode, observations stopped yielding")
|
||||||
|
|
||||||
|
def replay_attack(self, obs: Observation) -> int:
|
||||||
|
possible_combos = 1
|
||||||
|
for el in self.possible_nkode:
|
||||||
|
possible_combos *= len({obs.flat_keypad.index(el2) // self.properties_per_key for el2 in el})
|
||||||
|
return possible_combos
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Keypad:
|
|
||||||
keypad: np.ndarray
|
|
||||||
k: int # number of keys
|
|
||||||
p: int # properties per key
|
|
||||||
keypad_cache: list #
|
|
||||||
max_cache_size: int = 100
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def new_keypad(k: int, p: int):
|
|
||||||
total_properties = k * p
|
|
||||||
array = np.arange(total_properties)
|
|
||||||
# Reshape into a 3x4 matrix
|
|
||||||
keypad = array.reshape(k, p)
|
|
||||||
set_view = keypad.T
|
|
||||||
|
|
||||||
for set_idx in set_view:
|
|
||||||
np.random.shuffle(set_idx)
|
|
||||||
|
|
||||||
return Keypad(keypad=set_view.T, k=k, p=p, keypad_cache=[])
|
|
||||||
|
|
||||||
def split_shuffle(self):
|
|
||||||
"""
|
|
||||||
This is a modified split shuffle.
|
|
||||||
It doesn't shuffle the keys only the properties in the keys.
|
|
||||||
Shuffling the keys makes it hard for people to guess an nKode not a machine.
|
|
||||||
This split shuffle includes a cache to prevent the same configuration from being used.
|
|
||||||
This cache is not in any other implementation.
|
|
||||||
Testing suggests it's not necessary.
|
|
||||||
Getting the same keypad twice over 100 shuffles is very unlikely.
|
|
||||||
"""
|
|
||||||
shuffled_sets = self._shuffle()
|
|
||||||
# Sort the shuffled sets by the first column
|
|
||||||
sorted_set = shuffled_sets[np.argsort(shuffled_sets[:, 0])]
|
|
||||||
while str(sorted_set) in self.keypad_cache:
|
|
||||||
# continue shuffling until we get a unique configuration
|
|
||||||
shuffled_sets = self._shuffle()
|
|
||||||
sorted_set = shuffled_sets[np.argsort(shuffled_sets[:, 0])]
|
|
||||||
|
|
||||||
self.keypad_cache.append(str(sorted_set))
|
|
||||||
self.keypad_cache = self.keypad_cache[:self.max_cache_size]
|
|
||||||
self.keypad = shuffled_sets
|
|
||||||
|
|
||||||
|
|
||||||
def _shuffle(self) -> np.ndarray:
|
|
||||||
column_permutation = np.random.permutation(self.p)
|
|
||||||
column_subset = column_permutation[:self.p // 2]
|
|
||||||
perm_indices = np.random.permutation(self.k)
|
|
||||||
shuffled_sets = self.keypad.copy()
|
|
||||||
shuffled_sets[:, column_subset] = shuffled_sets[perm_indices, :][:, column_subset]
|
|
||||||
return shuffled_sets
|
|
||||||
|
|
||||||
def full_shuffle(self):
|
|
||||||
shuffled_matrix = np.array([np.random.permutation(row) for row in self.keypad.T])
|
|
||||||
self.keypad = shuffled_matrix.T
|
|
||||||
|
|
||||||
def key_entry(self, target_passcode: list[int]) -> list[int]:
|
|
||||||
"""
|
|
||||||
Given target_values, return the row indices they are in.
|
|
||||||
Assert that each element is >= 0 and < self.k * self.p.
|
|
||||||
"""
|
|
||||||
# Convert the list to a NumPy array for vectorized checks
|
|
||||||
vals = np.array(target_passcode)
|
|
||||||
|
|
||||||
# Validate that each value is within the valid range
|
|
||||||
if np.any((vals < 0) | (vals >= self.k * self.p)):
|
|
||||||
raise ValueError("One or more values are out of the valid range.")
|
|
||||||
|
|
||||||
# Flatten the keypad to a 1D array
|
|
||||||
flat = self.keypad.flatten()
|
|
||||||
|
|
||||||
# Create an inverse mapping from value -> row index
|
|
||||||
inv_index = np.empty(self.k * self.p, dtype=int)
|
|
||||||
# Each value v is at position i in 'flat', so row = i // p
|
|
||||||
for i, v in enumerate(flat):
|
|
||||||
inv_index[v] = i // self.p
|
|
||||||
|
|
||||||
# Use the inverse mapping to get row indices for all target values
|
|
||||||
return inv_index[vals].tolist()
|
|
||||||
0
src/keypad/__init__.py
Normal file
123
src/keypad/keypad.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import numpy as np
|
||||||
|
from src.keypad.tower_shuffle import TowerShuffle
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
|
||||||
|
class BaseKeypad(ABC):
|
||||||
|
keypad: np.ndarray
|
||||||
|
k: int # number of keys
|
||||||
|
p: int # properties per key
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_keypad(cls, k: int, p: int) -> np.ndarray:
|
||||||
|
rng = np.random.default_rng()
|
||||||
|
total = k * p
|
||||||
|
array = np.arange(total)
|
||||||
|
keypad = array.reshape(k, p)
|
||||||
|
set_view = keypad.T.copy()
|
||||||
|
for set_idx in set_view:
|
||||||
|
rng.shuffle(set_idx)
|
||||||
|
return set_view.T
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def new_keypad(cls, k: int, p: int) -> Self:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def key_entry(self, target_passcode: list[int]) -> list[int]:
|
||||||
|
vals = np.array(target_passcode)
|
||||||
|
if np.any((vals < 0) | (vals >= self.k * self.p)):
|
||||||
|
raise ValueError("One or more values are out of the valid range.")
|
||||||
|
flat = self.keypad.flatten()
|
||||||
|
inv_index = np.empty(self.k * self.p, dtype=int)
|
||||||
|
for i, v in enumerate(flat):
|
||||||
|
inv_index[v] = i // self.p
|
||||||
|
return inv_index[vals].tolist()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def shuffle(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def keypad_mat(self) -> list[list[int]]:
|
||||||
|
return [el.tolist() for el in self.keypad]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SlidingTowerShuffleKeypad(BaseKeypad):
|
||||||
|
keypad: np.ndarray
|
||||||
|
k: int # number of keys
|
||||||
|
p: int # properties per key
|
||||||
|
tower_shuffle: TowerShuffle
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_keypad(cls, k: int, p: int) -> Self:
|
||||||
|
kp = cls._build_keypad(k, p)
|
||||||
|
return cls(keypad=kp, k=k, p=p, tower_shuffle=TowerShuffle.new(p))
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
selected_positions = self.tower_shuffle.left_tower.tolist()
|
||||||
|
shift = np.random.randint(1, self.k) # random int in [1, k-1]
|
||||||
|
new_key_idxs = np.roll(np.arange(self.k), shift)
|
||||||
|
shuffled_sets = self.keypad.copy()
|
||||||
|
shuffled_sets[:, selected_positions] = shuffled_sets[new_key_idxs, :][:, selected_positions]
|
||||||
|
self.keypad = shuffled_sets
|
||||||
|
self.tower_shuffle.shuffle()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RandomShuffleKeypad(BaseKeypad):
|
||||||
|
keypad: np.ndarray
|
||||||
|
k: int # number of keys
|
||||||
|
p: int # properties per key
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_keypad(cls, k: int, p: int) -> Self:
|
||||||
|
kp = cls._build_keypad(k, p)
|
||||||
|
return cls(keypad=kp, k=k, p=p)
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
shuffled_matrix = np.array([np.random.permutation(row) for row in self.keypad.T])
|
||||||
|
self.keypad = shuffled_matrix.T
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RandomSplitShuffleKeypad(BaseKeypad):
|
||||||
|
keypad: np.ndarray
|
||||||
|
k: int # number of keys
|
||||||
|
p: int # properties per key
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_keypad(cls, k: int, p: int) -> Self:
|
||||||
|
kp = cls._build_keypad(k, p)
|
||||||
|
return cls(keypad=kp, k=k, p=p)
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
column_permutation = np.random.permutation(self.p)
|
||||||
|
column_subset = column_permutation[:self.p // 2]
|
||||||
|
new_key_idxs = np.random.permutation(self.k)
|
||||||
|
shuffled_sets = self.keypad.copy()
|
||||||
|
shuffled_sets[:, column_subset] = shuffled_sets[new_key_idxs, :][:, column_subset]
|
||||||
|
self.keypad = shuffled_sets
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SlidingSplitShuffleKeypad(BaseKeypad):
|
||||||
|
keypad: np.ndarray
|
||||||
|
k: int # number of keys
|
||||||
|
p: int # properties per key
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_keypad(cls, k: int, p: int) -> Self:
|
||||||
|
kp = cls._build_keypad(k, p)
|
||||||
|
return cls(keypad=kp, k=k, p=p)
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
selected_positions = np.random.permutation(self.p)
|
||||||
|
column_subset = selected_positions[:self.p // 2]
|
||||||
|
shift = np.random.randint(1, self.k)
|
||||||
|
new_key_idxs = np.roll(np.arange(self.k), shift)
|
||||||
|
shuffled_sets = self.keypad.copy()
|
||||||
|
shuffled_sets[:, column_subset] = shuffled_sets[new_key_idxs, :][:, column_subset]
|
||||||
|
self.keypad = shuffled_sets
|
||||||
80
src/keypad/tower_shuffle.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Tower:
|
||||||
|
floors: list[np.ndarray]
|
||||||
|
|
||||||
|
def split_tower(self) -> tuple[np.ndarray, np.ndarray]:
|
||||||
|
discard = np.array([], dtype=int)
|
||||||
|
keep = np.array([], dtype=int)
|
||||||
|
balance = self.balance()
|
||||||
|
for idx, floor in enumerate(self.floors):
|
||||||
|
div = len(floor)//2 + balance[idx]
|
||||||
|
floor_shuffle = np.random.permutation(len(floor))
|
||||||
|
keep = np.concatenate((keep, floor[floor_shuffle[:div]]))
|
||||||
|
discard = np.concatenate((discard, floor[floor_shuffle[div:]]))
|
||||||
|
diff = len(discard) - len(keep)
|
||||||
|
assert 0 <= diff <= 1
|
||||||
|
return keep, discard
|
||||||
|
|
||||||
|
def balance(self) -> list[int]:
|
||||||
|
odd_floors = np.array([idx for idx, el in enumerate(self.floors) if len(el) & 1])
|
||||||
|
balance = np.zeros(len(self.floors), dtype=int)
|
||||||
|
if len(odd_floors) == 0:
|
||||||
|
return balance.tolist()
|
||||||
|
shuffle = np.random.permutation(len(odd_floors))[:len(odd_floors) // 2]
|
||||||
|
odd_floors = odd_floors[shuffle]
|
||||||
|
balance[odd_floors] = 1
|
||||||
|
return balance.tolist()
|
||||||
|
|
||||||
|
|
||||||
|
def update_tower(self, keep: np.ndarray, other_discard: np.ndarray):
|
||||||
|
new_floors = []
|
||||||
|
for floor in self.floors:
|
||||||
|
new_floor = np.intersect1d(floor, keep)
|
||||||
|
if len(new_floor):
|
||||||
|
new_floors.append(new_floor)
|
||||||
|
self.floors = new_floors
|
||||||
|
self.floors.insert(0, other_discard)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
str_val = ""
|
||||||
|
floor_numb = [i for i in reversed(range(len(self.floors)))]
|
||||||
|
for idx, val in enumerate(reversed(self.floors)):
|
||||||
|
str_val += f"Floor {floor_numb[idx]}: {val.tolist()}\n"
|
||||||
|
return str_val
|
||||||
|
|
||||||
|
def tolist(self) -> list[int]:
|
||||||
|
tower = []
|
||||||
|
for floor in self.floors:
|
||||||
|
tower.extend(floor.tolist())
|
||||||
|
return tower
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TowerShuffle:
|
||||||
|
total_positions: int
|
||||||
|
left_tower: Tower
|
||||||
|
right_tower: Tower
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls, total_pos:int):
|
||||||
|
assert total_pos >= 3
|
||||||
|
rand_pos = np.random.permutation(total_pos)
|
||||||
|
return TowerShuffle(
|
||||||
|
total_positions=total_pos,
|
||||||
|
left_tower=Tower(floors=[rand_pos[:total_pos//2]]),
|
||||||
|
right_tower=Tower(floors=[rand_pos[total_pos//2:]]),
|
||||||
|
)
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
left_keep, left_discard = self.left_tower.split_tower()
|
||||||
|
right_keep, right_discard = self.right_tower.split_tower()
|
||||||
|
self.left_tower.update_tower(left_keep, right_discard)
|
||||||
|
self.right_tower.update_tower(right_keep, left_discard)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"""Left Tower:
|
||||||
|
{self.left_tower}
|
||||||
|
Right Tower:
|
||||||
|
{self.right_tower}"""
|
||||||
47
src/utils.py
@@ -1,62 +1,49 @@
|
|||||||
import random
|
import random
|
||||||
from enum import Enum
|
|
||||||
from math import factorial, comb
|
from math import factorial, comb
|
||||||
|
|
||||||
from src.evilkode import Observation
|
from src.evilnkode import Observation
|
||||||
from src.keypad import Keypad
|
from src.keypad.keypad import BaseKeypad
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
|
|
||||||
def total_valid_nkode_states(k: int, p: int) -> int:
|
def total_valid_nkode_states(k: int, p: int) -> int:
|
||||||
return factorial(k) ** (p-1)
|
return factorial(k) ** (p - 1)
|
||||||
|
|
||||||
|
|
||||||
def total_shuffle_states(k: int, p: int) -> int:
|
def total_shuffle_states(k: int, p: int) -> int:
|
||||||
return comb((p-1), (p-1) // 2) * factorial(k)
|
return comb((p - 1), (p - 1) // 2) * factorial(k)
|
||||||
|
|
||||||
|
|
||||||
class ShuffleTypes(Enum):
|
def observations(target_passcode: list[int], keypad: BaseKeypad, number_of_observations: int = 100) -> Iterator[
|
||||||
FULL_SHUFFLE = "FULL_SHUFFLE"
|
Observation]:
|
||||||
SPLIT_SHUFFLE = "SPLIT_SHUFFLE"
|
def obs():
|
||||||
|
|
||||||
|
|
||||||
def observations(target_passcode: list[int], number_of_keys:int, properties_per_key: int, min_complexity: int, min_disparity: int, shuffle_type: ShuffleTypes, number_of_observations: int = 100):
|
|
||||||
k = number_of_keys
|
|
||||||
p = properties_per_key
|
|
||||||
keypad = Keypad.new_keypad(k, p)
|
|
||||||
|
|
||||||
def obs_gen():
|
|
||||||
for _ in range(number_of_observations):
|
for _ in range(number_of_observations):
|
||||||
yield Observation(
|
yield Observation(
|
||||||
keypad=keypad.keypad.copy(),
|
keypad=keypad.keypad_mat(),
|
||||||
key_selection=keypad.key_entry(target_passcode=target_passcode)
|
key_selection=keypad.key_entry(target_passcode=target_passcode)
|
||||||
)
|
)
|
||||||
match shuffle_type:
|
keypad.shuffle()
|
||||||
case ShuffleTypes.FULL_SHUFFLE:
|
|
||||||
keypad.full_shuffle()
|
|
||||||
case ShuffleTypes.SPLIT_SHUFFLE:
|
|
||||||
keypad.split_shuffle()
|
|
||||||
case _:
|
|
||||||
raise Exception(f"no shuffle type {shuffle_type}")
|
|
||||||
|
|
||||||
return obs_gen()
|
return obs()
|
||||||
|
|
||||||
|
|
||||||
def passcode_generator(k: int, p: int, n: int, c: int, d: int) -> list[int]:
|
def passcode_generator(k: int, p: int, n: int, c: int, d: int) -> list[int]:
|
||||||
assert n >= c
|
assert n >= c
|
||||||
assert p*k >= c
|
assert p * k >= c
|
||||||
|
|
||||||
assert n >= d
|
assert n >= d
|
||||||
assert p >= d
|
assert p >= d
|
||||||
passcode_prop = []
|
passcode_prop = []
|
||||||
passcode_set = []
|
passcode_set = []
|
||||||
valid_choices = {i for i in range(k*p)}
|
valid_choices = {i for i in range(k * p)}
|
||||||
repeat_set = n-d
|
repeat_set = n - d
|
||||||
repeat_prop = n-c
|
repeat_prop = n - c
|
||||||
prop_added = set()
|
prop_added = set()
|
||||||
set_added = set()
|
set_added = set()
|
||||||
|
|
||||||
for _ in range(n):
|
for _ in range(n):
|
||||||
prop = random.choice(list(valid_choices))
|
prop = random.choice(list(valid_choices))
|
||||||
prop_set = prop//p
|
prop_set = prop // p
|
||||||
passcode_prop.append(prop)
|
passcode_prop.append(prop)
|
||||||
passcode_set.append(prop_set)
|
passcode_set.append(prop_set)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
from src.utils import passcode_generator
|
from src.utils import passcode_generator
|
||||||
|
from src.keypad.keypad import (
|
||||||
|
RandomShuffleKeypad,
|
||||||
|
RandomSplitShuffleKeypad,
|
||||||
|
SlidingTowerShuffleKeypad,
|
||||||
|
SlidingSplitShuffleKeypad
|
||||||
|
)
|
||||||
|
from src.benchmark import benchmark
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"k, p, n, c, d, runs",
|
"k, p, n, c, d, runs",
|
||||||
[
|
[
|
||||||
@@ -10,6 +18,29 @@ import pytest
|
|||||||
def test_passcode_generator(k, p, n, c, d, runs):
|
def test_passcode_generator(k, p, n, c, d, runs):
|
||||||
for _ in range(runs):
|
for _ in range(runs):
|
||||||
passcode = passcode_generator(k=k, p=p, n=n, c=c, d=d)
|
passcode = passcode_generator(k=k, p=p, n=n, c=c, d=d)
|
||||||
passcode_sets = [el//p for el in passcode]
|
passcode_sets = [el // p for el in passcode]
|
||||||
assert c <= len(set(passcode))
|
assert c <= len(set(passcode))
|
||||||
assert d <= len(set(passcode_sets))
|
assert d <= len(set(passcode_sets))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"number_of_keys,properties_per_key,passcode_len,max_tries_before_lockout,complexity,disparity,run_count,keypad",
|
||||||
|
[
|
||||||
|
(6, 8, 4, 5, 4, 4, 100, RandomShuffleKeypad),
|
||||||
|
(6, 8, 4, 5, 4, 4, 100, RandomSplitShuffleKeypad),
|
||||||
|
(6, 8, 4, 5, 4, 4, 100, SlidingSplitShuffleKeypad),
|
||||||
|
(6, 8, 4, 5, 4, 4, 100, SlidingTowerShuffleKeypad),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_benchmark(number_of_keys, properties_per_key, passcode_len, max_tries_before_lockout, complexity, disparity,
|
||||||
|
run_count, keypad):
|
||||||
|
benchmark(
|
||||||
|
number_of_keys=number_of_keys,
|
||||||
|
properties_per_key=properties_per_key,
|
||||||
|
passcode_len=passcode_len,
|
||||||
|
max_tries_before_lockout=max_tries_before_lockout,
|
||||||
|
run_count=run_count,
|
||||||
|
complexity=complexity,
|
||||||
|
disparity=disparity,
|
||||||
|
keypad=keypad.new_keypad(number_of_keys, properties_per_key)
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,45 +1,34 @@
|
|||||||
import random
|
import random
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.evilkode import Evilkode, Observation
|
from src.evilnkode import EvilNKode
|
||||||
from src.keypad import Keypad
|
from src.keypad.keypad import (
|
||||||
|
RandomShuffleKeypad,
|
||||||
|
)
|
||||||
|
from src.utils import observations
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def observations(number_of_keys, properties_per_key, passcode_len):
|
def passcode(number_of_keys, properties_per_key, passcode_len):
|
||||||
k = number_of_keys
|
return [random.randint(0, number_of_keys * properties_per_key - 1) for _ in range(passcode_len)]
|
||||||
p = properties_per_key
|
|
||||||
n = passcode_len
|
|
||||||
nkode = [random.randint(0, k*p-1) for _ in range(n)]
|
|
||||||
keypad = Keypad.new_keypad(k, p)
|
|
||||||
|
|
||||||
def obs_gen():
|
|
||||||
for _ in range(100): # finite number of yields
|
|
||||||
yield Observation(
|
|
||||||
keypad=keypad.keypad.copy(),
|
|
||||||
key_selection=keypad.key_entry(target_passcode=nkode)
|
|
||||||
)
|
|
||||||
keypad.split_shuffle()
|
|
||||||
|
|
||||||
return obs_gen()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"number_of_keys, properties_per_key, passcode_len",
|
"number_of_keys, properties_per_key, passcode_len",
|
||||||
[
|
[
|
||||||
(5, 3, 4), # Test case 1
|
(5, 4, 4), # Test case 1
|
||||||
(10, 5, 6), # Test case 2
|
(10, 5, 6), # Test case 2
|
||||||
(8, 4, 5), # Test case 3
|
(8, 4, 5), # Test case 3
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_evilkode(number_of_keys, properties_per_key, passcode_len, observations):
|
def test_evilkode(number_of_keys, properties_per_key, passcode_len, passcode):
|
||||||
evilkode = Evilkode(
|
keypad = RandomShuffleKeypad.new_keypad(number_of_keys, properties_per_key)
|
||||||
observations=observations,
|
obs = observations(passcode, keypad)
|
||||||
|
evilkode = EvilNKode(
|
||||||
|
observations=obs,
|
||||||
number_of_keys=number_of_keys,
|
number_of_keys=number_of_keys,
|
||||||
properties_per_key=properties_per_key,
|
properties_per_key=properties_per_key,
|
||||||
passcode_len=passcode_len,
|
passcode_len=passcode_len,
|
||||||
)
|
)
|
||||||
|
|
||||||
evilout = evilkode.run()
|
evilout = evilkode.run()
|
||||||
assert evilout.iterations > 1
|
assert evilout.iterations_to_break > 1
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
|
import pytest
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from src.keypad.keypad import (
|
||||||
|
RandomSplitShuffleKeypad,
|
||||||
|
RandomShuffleKeypad,
|
||||||
|
SlidingSplitShuffleKeypad,
|
||||||
|
SlidingTowerShuffleKeypad,
|
||||||
|
)
|
||||||
|
|
||||||
from src.keypad import Keypad
|
|
||||||
|
|
||||||
def test_keypad():
|
def test_key_entry():
|
||||||
keypad = Keypad(
|
keypad = RandomShuffleKeypad(
|
||||||
keypad=np.array([
|
keypad=np.array([
|
||||||
[8, 9, 10, 11],
|
[8, 9, 10, 11],
|
||||||
[0, 5, 2, 3],
|
[0, 5, 2, 3],
|
||||||
[4, 1, 6,7]
|
[4, 1, 6, 7]
|
||||||
]), k= 3, p=4, keypad_cache=[])
|
]), k=3, p=4)
|
||||||
|
assert keypad.key_entry([8, 5, 6, 11]) == [0, 1, 2, 0]
|
||||||
|
|
||||||
assert keypad.key_entry([8, 5, 6, 11]) == [0,1,2,0]
|
|
||||||
|
|
||||||
def test_split_shuffle():
|
@pytest.mark.parametrize(
|
||||||
p = 4 # properties_per_key
|
"keypad_type, number_of_keys, properties_per_key",
|
||||||
k = 3 # number_of_keys
|
[
|
||||||
keypad = Keypad.new_keypad(k, p)
|
(RandomShuffleKeypad, 3, 4),
|
||||||
|
(RandomSplitShuffleKeypad, 3, 4),
|
||||||
|
(SlidingTowerShuffleKeypad, 3, 4),
|
||||||
|
(SlidingSplitShuffleKeypad, 3, 4),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_keypad_shuffle(keypad_type, number_of_keys, properties_per_key):
|
||||||
|
keypad = keypad_type.new_keypad(number_of_keys, properties_per_key)
|
||||||
print(keypad.keypad)
|
print(keypad.keypad)
|
||||||
keypad.split_shuffle()
|
keypad.shuffle()
|
||||||
print(keypad.keypad)
|
print(keypad.keypad)
|
||||||
|
|
||||||
|
|
||||||
def test_full_shuffle():
|
|
||||||
p = 4 # properties_per_key
|
|
||||||
k = 3 # number_of_keys
|
|
||||||
keypad = Keypad.new_keypad(k, p)
|
|
||||||
print(keypad.keypad)
|
|
||||||
keypad.full_shuffle()
|
|
||||||
print(keypad.keypad)
|
|
||||||
|
|
||||||
|
|||||||
8
tests/test_tower_shuffle.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from src.keypad.tower_shuffle import TowerShuffle
|
||||||
|
|
||||||
|
def test_tower_shuffle():
|
||||||
|
tower = TowerShuffle.new(9)
|
||||||
|
print(tower)
|
||||||
|
for _ in range(100):
|
||||||
|
tower.shuffle()
|
||||||
|
print(tower)
|
||||||