Remove legacy continuous optimizer
This commit is contained in:
parent
d9fa411daf
commit
0808271d05
19 changed files with 360 additions and 3434 deletions
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import numpy as np
|
||||
|
||||
from .metrics import luminance
|
||||
from .types import ContinuousSourceAnalysis, TileGraphSourceAnalysis
|
||||
from .types import PhaseFieldSourceAnalysis, TileGraphSourceAnalysis
|
||||
|
||||
|
||||
def _require_torch():
|
||||
|
|
@ -55,23 +55,23 @@ def _compute_edge_map_torch(torch, rgba_t):
|
|||
return edge.to(dtype=torch.float32)
|
||||
|
||||
|
||||
def analyze_continuous_source(
|
||||
def analyze_phase_field_source(
|
||||
rgba: np.ndarray,
|
||||
seed: int,
|
||||
device: str | None = None,
|
||||
) -> ContinuousSourceAnalysis:
|
||||
) -> PhaseFieldSourceAnalysis:
|
||||
del seed
|
||||
if device is not None:
|
||||
torch = _require_torch()
|
||||
resolved_device = _resolve_device(torch, device)
|
||||
rgba_t = torch.from_numpy(rgba).to(device=resolved_device, dtype=torch.float32)
|
||||
edge_map_t = _compute_edge_map_torch(torch, rgba_t)
|
||||
return ContinuousSourceAnalysis(
|
||||
return PhaseFieldSourceAnalysis(
|
||||
edge_map=edge_map_t.detach().cpu().numpy().astype(np.float32),
|
||||
)
|
||||
|
||||
edge_map = _compute_edge_map(rgba)
|
||||
return ContinuousSourceAnalysis(
|
||||
return PhaseFieldSourceAnalysis(
|
||||
edge_map=edge_map,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
target.add_argument("--device", default="auto", choices=("auto", "cpu", "cuda"), help="Torch device")
|
||||
target.add_argument(
|
||||
"--reconstruction-mode",
|
||||
default="continuous",
|
||||
choices=("continuous", "phase-field", "tile-graph"),
|
||||
default="phase-field",
|
||||
choices=("phase-field", "tile-graph"),
|
||||
help="Reconstruction engine to run after lattice estimation",
|
||||
)
|
||||
target.add_argument(
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ def run_compare(
|
|||
steps: int = 200,
|
||||
device: str = "auto",
|
||||
strip_background: bool = False,
|
||||
reconstruction_mode: str = "continuous",
|
||||
reconstruction_mode: str = "phase-field",
|
||||
enable_phase_rerank: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
diagnostics_path = Path(diagnostics_dir) if diagnostics_dir else Path(output_path).with_suffix("")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,59 +18,7 @@ class SolverHyperParams:
|
|||
phase_field_magnitude_weight: float = 0.08
|
||||
phase_field_learning_rate: float = 0.10
|
||||
phase_field_max_displacement_ratio: float = 0.48
|
||||
representative_softmax_scale: float = 18.0
|
||||
source_edge_reliability_gain: float = 1.6
|
||||
source_edge_reliability_floor: float = 0.25
|
||||
source_edge_alpha_floor: float = 0.45
|
||||
source_edge_detail_mix: float = 0.65
|
||||
source_edge_detail_threshold: float = 0.25
|
||||
boundary_probe_scale: float = 0.22
|
||||
boundary_signed_weight: float = 0.55
|
||||
boundary_direction_weight: float = 0.30
|
||||
boundary_magnitude_weight: float = 0.15
|
||||
snap_base_match_weight: float = 0.65
|
||||
snap_representative_match_weight: float = 0.15
|
||||
snap_source_match_weight: float = 0.85
|
||||
snap_neighbor_weight: float = 0.35
|
||||
snap_diagonal_weight: float = 0.14
|
||||
snap_representative_delta_weight: float = 0.20
|
||||
snap_source_delta_weight: float = 0.80
|
||||
refine_anchor_weight: float = 0.60
|
||||
refine_source_weight: float = 0.20
|
||||
refine_alpha_weight: float = 0.10
|
||||
refine_distance_weight: float = 0.10
|
||||
refine_source_delta_weight: float = 0.25
|
||||
refine_orthogonal_weight: float = 0.24
|
||||
refine_diagonal_weight: float = 0.08
|
||||
refine_motif_weight: float = 0.12
|
||||
refine_line_weight: float = 0.10
|
||||
refine_candidate_extent: float = 0.70
|
||||
refine_candidate_levels: int = 7
|
||||
guided_candidate_edge_threshold: float = 0.06
|
||||
guided_candidate_inner_scale: float = 0.32
|
||||
guided_candidate_outer_scale: float = 0.60
|
||||
relax_iterations: int = 24
|
||||
relax_start_temperature: float = 0.95
|
||||
relax_end_temperature: float = 0.18
|
||||
relax_damping: float = 0.10
|
||||
relax_anchor_scale: float = 0.50
|
||||
relax_orthogonal_weight: float = 0.34
|
||||
relax_diagonal_weight: float = 0.12
|
||||
relax_source_adjacency_weight: float = 0.35
|
||||
relax_motif_weight: float = 0.22
|
||||
relax_line_weight: float = 0.06
|
||||
relax_source_motif_weight: float = 0.30
|
||||
relax_source_line_weight: float = 0.12
|
||||
relax_handoff_weight: float = 0.18
|
||||
structure_boundary_weight: float = 0.60
|
||||
structure_anchor_adjacency_weight: float = 0.15
|
||||
structure_anchor_motif_weight: float = 0.20
|
||||
structure_anchor_line_weight: float = 0.10
|
||||
structure_source_adjacency_weight: float = 0.35
|
||||
structure_source_motif_weight: float = 0.40
|
||||
structure_source_line_weight: float = 0.20
|
||||
alpha_foreground_threshold: float = 0.50
|
||||
alpha_representative_foreground_threshold: float = 0.60
|
||||
alpha_opaque_threshold: float = 0.95
|
||||
alpha_transparent_threshold: float = 0.05
|
||||
phase_rerank_support_weight: float = 0.45
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|||
import numpy as np
|
||||
|
||||
from .io import premultiply
|
||||
from .types import ContinuousSourceAnalysis, InferenceResult, SolverArtifacts
|
||||
from .types import InferenceResult, PhaseFieldSourceAnalysis, SolverArtifacts
|
||||
from .params import SolverHyperParams
|
||||
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ def _prepare_phase_field(
|
|||
F,
|
||||
rgba: np.ndarray,
|
||||
inference: InferenceResult,
|
||||
analysis: ContinuousSourceAnalysis,
|
||||
analysis: PhaseFieldSourceAnalysis,
|
||||
solver_params: SolverHyperParams,
|
||||
*,
|
||||
device: str,
|
||||
|
|
@ -379,7 +379,7 @@ def _nearest_source_rgba(source_rgba: np.ndarray, sample_x: np.ndarray, sample_y
|
|||
def optimize_phase_field(
|
||||
rgba: np.ndarray,
|
||||
inference: InferenceResult,
|
||||
analysis: ContinuousSourceAnalysis,
|
||||
analysis: PhaseFieldSourceAnalysis,
|
||||
steps: int,
|
||||
seed: int,
|
||||
device: str,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ from typing import Any
|
|||
|
||||
import numpy as np
|
||||
|
||||
from .analysis import analyze_continuous_source, analyze_tile_graph_source
|
||||
from .continuous import optimize_lattice_pixels
|
||||
from .analysis import analyze_phase_field_source, analyze_tile_graph_source
|
||||
from .diagnostics import (
|
||||
summarize_run,
|
||||
write_alpha_preview,
|
||||
|
|
@ -31,7 +30,7 @@ from .phase_field import optimize_phase_field
|
|||
from .palette import load_palette, quantize_rgba, save_palette_report
|
||||
from .preprocess import strip_edge_background
|
||||
from .tile_graph import optimize_tile_graph
|
||||
from .types import ContinuousSourceAnalysis, InferenceResult, RunResult, TileGraphSourceAnalysis
|
||||
from .types import InferenceResult, PhaseFieldSourceAnalysis, RunResult, TileGraphSourceAnalysis
|
||||
|
||||
|
||||
def run_pipeline(
|
||||
|
|
@ -51,7 +50,7 @@ def run_pipeline(
|
|||
device: str = "auto",
|
||||
solver_params: SolverHyperParams | None = None,
|
||||
strip_background: bool = False,
|
||||
reconstruction_mode: str = "continuous",
|
||||
reconstruction_mode: str = "phase-field",
|
||||
enable_phase_rerank: bool = True,
|
||||
) -> RunResult:
|
||||
started = time.perf_counter()
|
||||
|
|
@ -191,10 +190,10 @@ def _prepare_analysis(
|
|||
seed: int,
|
||||
device: str,
|
||||
reconstruction_mode: str,
|
||||
) -> ContinuousSourceAnalysis | TileGraphSourceAnalysis:
|
||||
) -> PhaseFieldSourceAnalysis | TileGraphSourceAnalysis:
|
||||
if reconstruction_mode == "tile-graph":
|
||||
return analyze_tile_graph_source(source, device=device)
|
||||
return analyze_continuous_source(source, seed=seed)
|
||||
return analyze_phase_field_source(source, seed=seed, device=device)
|
||||
|
||||
|
||||
def _select_phase_candidate(
|
||||
|
|
@ -205,7 +204,7 @@ def _select_phase_candidate(
|
|||
seed: int,
|
||||
device: str,
|
||||
solver_params: SolverHyperParams | None = None,
|
||||
reconstruction_mode: str = "continuous",
|
||||
reconstruction_mode: str = "phase-field",
|
||||
enable_phase_rerank: bool = True,
|
||||
) -> InferenceResult:
|
||||
return _select_phase_candidate_with_reconstruction(
|
||||
|
|
@ -228,13 +227,13 @@ def _select_phase_candidate_with_reconstruction(
|
|||
seed: int,
|
||||
device: str,
|
||||
solver_params: SolverHyperParams | None = None,
|
||||
reconstruction_mode: str = "continuous",
|
||||
reconstruction_mode: str = "phase-field",
|
||||
enable_phase_rerank: bool = True,
|
||||
) -> InferenceResult:
|
||||
solver_params = solver_params or SolverHyperParams()
|
||||
if not enable_phase_rerank:
|
||||
return inference
|
||||
if reconstruction_mode != "continuous":
|
||||
if reconstruction_mode != "phase-field":
|
||||
return inference
|
||||
if len(inference.top_candidates) <= 1 or inference.confidence >= solver_params.phase_rerank_confidence_threshold:
|
||||
return inference
|
||||
|
|
@ -451,20 +450,12 @@ def _run_reconstruction(
|
|||
device=device,
|
||||
solver_params=solver_params,
|
||||
)
|
||||
phase_field_metrics = getattr(solver, "stage_diagnostics", {}).get("phase_field", {})
|
||||
return solver, {
|
||||
"mode": "phase-field",
|
||||
**{
|
||||
f"phase_field_{key}": value
|
||||
for key, value in solver.stage_diagnostics.get("phase_field", {}).items()
|
||||
for key, value in phase_field_metrics.items()
|
||||
},
|
||||
}
|
||||
solver = optimize_lattice_pixels(
|
||||
source,
|
||||
inference=inference,
|
||||
analysis=analysis,
|
||||
steps=steps,
|
||||
seed=seed,
|
||||
device=device,
|
||||
solver_params=solver_params,
|
||||
)
|
||||
return solver, {"mode": "continuous"}
|
||||
raise ValueError(f"Unknown reconstruction mode: {reconstruction_mode}")
|
||||
|
|
|
|||
|
|
@ -38,17 +38,6 @@ def _score_summary(summary: dict[str, Any]) -> dict[str, float]:
|
|||
}
|
||||
|
||||
|
||||
def _normalize_group(values: np.ndarray) -> np.ndarray:
|
||||
values = np.clip(values.astype(np.float64), 1e-4, None)
|
||||
return values / np.sum(values)
|
||||
|
||||
|
||||
def _mutate_group(rng: np.random.Generator, values: list[float], scale: float) -> np.ndarray:
|
||||
base = np.asarray(values, dtype=np.float64)
|
||||
noise = np.exp(rng.normal(0.0, 0.28 * scale, size=base.shape))
|
||||
return _normalize_group(base * noise)
|
||||
|
||||
|
||||
def _mutate_positive(
|
||||
rng: np.random.Generator,
|
||||
value: float,
|
||||
|
|
@ -75,162 +64,160 @@ def _mutate_linear(
|
|||
|
||||
|
||||
def _mutate_params(base: SolverHyperParams, rng: np.random.Generator, scale: float) -> SolverHyperParams:
|
||||
refine_group = _mutate_group(
|
||||
rng,
|
||||
[
|
||||
base.refine_anchor_weight,
|
||||
base.refine_source_weight,
|
||||
base.refine_alpha_weight,
|
||||
base.refine_distance_weight,
|
||||
],
|
||||
scale,
|
||||
)
|
||||
structure_group = _mutate_group(
|
||||
rng,
|
||||
[
|
||||
base.structure_boundary_weight,
|
||||
base.structure_anchor_adjacency_weight,
|
||||
base.structure_anchor_motif_weight,
|
||||
base.structure_anchor_line_weight,
|
||||
],
|
||||
scale,
|
||||
)
|
||||
return replace(
|
||||
base,
|
||||
representative_softmax_scale=_mutate_positive(
|
||||
phase_field_patch_extent=_mutate_linear(
|
||||
rng,
|
||||
base.representative_softmax_scale,
|
||||
base.phase_field_patch_extent,
|
||||
scale=scale,
|
||||
low=8.0,
|
||||
high=36.0,
|
||||
),
|
||||
boundary_probe_scale=_mutate_linear(
|
||||
rng,
|
||||
base.boundary_probe_scale,
|
||||
scale=scale,
|
||||
sigma=0.03,
|
||||
low=0.14,
|
||||
high=0.32,
|
||||
),
|
||||
refine_anchor_weight=float(refine_group[0]),
|
||||
refine_source_weight=float(refine_group[1]),
|
||||
refine_alpha_weight=float(refine_group[2]),
|
||||
refine_distance_weight=float(refine_group[3]),
|
||||
refine_source_delta_weight=_mutate_linear(
|
||||
rng,
|
||||
base.refine_source_delta_weight,
|
||||
scale=scale,
|
||||
sigma=0.08,
|
||||
sigma=0.035,
|
||||
low=0.05,
|
||||
high=0.45,
|
||||
),
|
||||
refine_orthogonal_weight=_mutate_linear(
|
||||
rng,
|
||||
base.refine_orthogonal_weight,
|
||||
scale=scale,
|
||||
sigma=0.05,
|
||||
low=0.10,
|
||||
high=0.40,
|
||||
),
|
||||
refine_diagonal_weight=_mutate_linear(
|
||||
rng,
|
||||
base.refine_diagonal_weight,
|
||||
scale=scale,
|
||||
sigma=0.025,
|
||||
low=0.02,
|
||||
high=0.18,
|
||||
),
|
||||
refine_motif_weight=_mutate_linear(
|
||||
rng,
|
||||
base.refine_motif_weight,
|
||||
scale=scale,
|
||||
sigma=0.05,
|
||||
low=0.02,
|
||||
high=0.40,
|
||||
),
|
||||
refine_line_weight=_mutate_linear(
|
||||
rng,
|
||||
base.refine_line_weight,
|
||||
scale=scale,
|
||||
sigma=0.04,
|
||||
low=0.02,
|
||||
high=0.30,
|
||||
),
|
||||
relax_anchor_scale=_mutate_linear(
|
||||
rng,
|
||||
base.relax_anchor_scale,
|
||||
scale=scale,
|
||||
sigma=0.16,
|
||||
low=0.15,
|
||||
high=1.0,
|
||||
),
|
||||
relax_orthogonal_weight=_mutate_linear(
|
||||
rng,
|
||||
base.relax_orthogonal_weight,
|
||||
scale=scale,
|
||||
sigma=0.08,
|
||||
low=0.10,
|
||||
high=0.70,
|
||||
),
|
||||
relax_diagonal_weight=_mutate_linear(
|
||||
rng,
|
||||
base.relax_diagonal_weight,
|
||||
scale=scale,
|
||||
sigma=0.05,
|
||||
low=0.02,
|
||||
high=0.35,
|
||||
),
|
||||
relax_source_adjacency_weight=_mutate_linear(
|
||||
phase_field_data_coherence_weight=_mutate_positive(
|
||||
rng,
|
||||
base.relax_source_adjacency_weight,
|
||||
base.phase_field_data_coherence_weight,
|
||||
scale=scale,
|
||||
sigma=0.10,
|
||||
low=0.05,
|
||||
high=0.80,
|
||||
low=0.15,
|
||||
high=3.0,
|
||||
),
|
||||
relax_source_motif_weight=_mutate_linear(
|
||||
phase_field_data_edge_weight=_mutate_positive(
|
||||
rng,
|
||||
base.relax_source_motif_weight,
|
||||
base.phase_field_data_edge_weight,
|
||||
scale=scale,
|
||||
sigma=0.10,
|
||||
low=0.05,
|
||||
high=0.80,
|
||||
high=2.5,
|
||||
),
|
||||
relax_source_line_weight=_mutate_linear(
|
||||
phase_field_data_center_edge_weight=_mutate_positive(
|
||||
rng,
|
||||
base.relax_source_line_weight,
|
||||
base.phase_field_data_center_edge_weight,
|
||||
scale=scale,
|
||||
sigma=0.08,
|
||||
low=0.02,
|
||||
low=0.10,
|
||||
high=3.0,
|
||||
),
|
||||
phase_field_spacing_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_field_spacing_weight,
|
||||
scale=scale,
|
||||
low=0.01,
|
||||
high=1.0,
|
||||
),
|
||||
phase_field_smoothness_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_field_smoothness_weight,
|
||||
scale=scale,
|
||||
low=0.01,
|
||||
high=1.2,
|
||||
),
|
||||
phase_field_edge_gate_strength=_mutate_positive(
|
||||
rng,
|
||||
base.phase_field_edge_gate_strength,
|
||||
scale=scale,
|
||||
low=1.0,
|
||||
high=30.0,
|
||||
),
|
||||
phase_field_collapse_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_field_collapse_weight,
|
||||
scale=scale,
|
||||
low=0.05,
|
||||
high=4.0,
|
||||
),
|
||||
phase_field_min_spacing_ratio=_mutate_linear(
|
||||
rng,
|
||||
base.phase_field_min_spacing_ratio,
|
||||
scale=scale,
|
||||
sigma=0.05,
|
||||
low=0.01,
|
||||
high=0.45,
|
||||
),
|
||||
structure_boundary_weight=float(structure_group[0]),
|
||||
structure_anchor_adjacency_weight=float(structure_group[1]),
|
||||
structure_anchor_motif_weight=float(structure_group[2]),
|
||||
structure_anchor_line_weight=float(structure_group[3]),
|
||||
structure_source_adjacency_weight=_mutate_linear(
|
||||
phase_field_max_spacing_ratio=_mutate_linear(
|
||||
rng,
|
||||
base.structure_source_adjacency_weight,
|
||||
base.phase_field_max_spacing_ratio,
|
||||
scale=scale,
|
||||
sigma=0.12,
|
||||
low=0.05,
|
||||
high=0.90,
|
||||
low=0.8,
|
||||
high=1.8,
|
||||
),
|
||||
structure_source_motif_weight=_mutate_linear(
|
||||
phase_field_magnitude_weight=_mutate_positive(
|
||||
rng,
|
||||
base.structure_source_motif_weight,
|
||||
base.phase_field_magnitude_weight,
|
||||
scale=scale,
|
||||
sigma=0.12,
|
||||
low=0.05,
|
||||
high=0.90,
|
||||
low=0.001,
|
||||
high=0.5,
|
||||
),
|
||||
structure_source_line_weight=_mutate_linear(
|
||||
phase_field_learning_rate=_mutate_positive(
|
||||
rng,
|
||||
base.structure_source_line_weight,
|
||||
base.phase_field_learning_rate,
|
||||
scale=scale,
|
||||
low=0.005,
|
||||
high=0.5,
|
||||
),
|
||||
phase_field_max_displacement_ratio=_mutate_linear(
|
||||
rng,
|
||||
base.phase_field_max_displacement_ratio,
|
||||
scale=scale,
|
||||
sigma=0.08,
|
||||
low=0.02,
|
||||
high=0.50,
|
||||
low=0.10,
|
||||
high=1.0,
|
||||
),
|
||||
phase_rerank_support_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_rerank_support_weight,
|
||||
scale=scale,
|
||||
low=0.05,
|
||||
high=1.5,
|
||||
),
|
||||
phase_rerank_edge_position_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_rerank_edge_position_weight,
|
||||
scale=scale,
|
||||
low=0.01,
|
||||
high=1.0,
|
||||
),
|
||||
phase_rerank_wobble_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_rerank_wobble_weight,
|
||||
scale=scale,
|
||||
low=0.01,
|
||||
high=1.0,
|
||||
),
|
||||
phase_rerank_edge_concentration_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_rerank_edge_concentration_weight,
|
||||
scale=scale,
|
||||
low=0.01,
|
||||
high=1.0,
|
||||
),
|
||||
phase_rerank_size_penalty_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_rerank_size_penalty_weight,
|
||||
scale=scale,
|
||||
low=0.01,
|
||||
high=1.0,
|
||||
),
|
||||
phase_rerank_inference_penalty_weight=_mutate_positive(
|
||||
rng,
|
||||
base.phase_rerank_inference_penalty_weight,
|
||||
scale=scale,
|
||||
low=0.0,
|
||||
high=0.5,
|
||||
),
|
||||
phase_rerank_confidence_threshold=_mutate_linear(
|
||||
rng,
|
||||
base.phase_rerank_confidence_threshold,
|
||||
scale=scale,
|
||||
sigma=0.035,
|
||||
low=0.0,
|
||||
high=0.5,
|
||||
),
|
||||
phase_rerank_max_size_delta_ratio=_mutate_linear(
|
||||
rng,
|
||||
base.phase_rerank_max_size_delta_ratio,
|
||||
scale=scale,
|
||||
sigma=0.08,
|
||||
low=0.05,
|
||||
high=1.0,
|
||||
),
|
||||
phase_rerank_margin=_mutate_linear(
|
||||
rng,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class InferenceResult:
|
|||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ContinuousSourceAnalysis:
|
||||
class PhaseFieldSourceAnalysis:
|
||||
edge_map: np.ndarray
|
||||
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ class TileGraphSourceAnalysis:
|
|||
edge_map: np.ndarray
|
||||
|
||||
|
||||
SourceAnalysis = ContinuousSourceAnalysis | TileGraphSourceAnalysis
|
||||
SourceAnalysis = PhaseFieldSourceAnalysis | TileGraphSourceAnalysis
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
|
@ -86,7 +86,7 @@ class RunResult:
|
|||
source_rgba: np.ndarray
|
||||
output_rgba: np.ndarray
|
||||
inference: InferenceResult
|
||||
analysis: ContinuousSourceAnalysis | TileGraphSourceAnalysis
|
||||
analysis: PhaseFieldSourceAnalysis | TileGraphSourceAnalysis
|
||||
solver: SolverArtifacts
|
||||
cleanup: CleanupArtifacts
|
||||
palette_result: PaletteResult | None
|
||||
|
|
|
|||
Loading…
Reference in a new issue