spyrrow 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of spyrrow might be problematic. Click here for more details.
- {spyrrow-0.6.0 → spyrrow-0.7.0}/Cargo.lock +19 -19
- {spyrrow-0.6.0 → spyrrow-0.7.0}/Cargo.toml +6 -4
- spyrrow-0.7.0/PKG-INFO +11 -0
- spyrrow-0.7.0/clippy.toml +4 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/docs/api.rst +2 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/docs/index.rst +2 -1
- {spyrrow-0.6.0 → spyrrow-0.7.0}/spyrrow.pyi +59 -4
- {spyrrow-0.6.0 → spyrrow-0.7.0}/src/lib.rs +131 -30
- {spyrrow-0.6.0 → spyrrow-0.7.0}/tests/test_basic.py +37 -9
- spyrrow-0.6.0/PKG-INFO +0 -68
- {spyrrow-0.6.0 → spyrrow-0.7.0}/.github/workflows/CI.yml +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/.gitignore +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/.readthedocs.yaml +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/LICENSE.txt +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/README.md +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/docs/Makefile +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/docs/conf.py +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/docs/make.bat +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/pyproject.toml +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/tests/utils.py +0 -0
- {spyrrow-0.6.0 → spyrrow-0.7.0}/uv.lock +0 -0
|
@@ -326,9 +326,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
|
326
326
|
|
|
327
327
|
[[package]]
|
|
328
328
|
name = "hermit-abi"
|
|
329
|
-
version = "0.
|
|
329
|
+
version = "0.5.2"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
|
332
332
|
|
|
333
333
|
[[package]]
|
|
334
334
|
name = "indoc"
|
|
@@ -359,8 +359,8 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
|
359
359
|
|
|
360
360
|
[[package]]
|
|
361
361
|
name = "jagua-rs"
|
|
362
|
-
version = "0.6.
|
|
363
|
-
source = "git+https://github.com/JeroenGar/jagua-rs.git?rev=
|
|
362
|
+
version = "0.6.3"
|
|
363
|
+
source = "git+https://github.com/JeroenGar/jagua-rs.git?rev=f8f18907a6a82310769eed60fa6d8c0e7aaea0d3#f8f18907a6a82310769eed60fa6d8c0e7aaea0d3"
|
|
364
364
|
dependencies = [
|
|
365
365
|
"anyhow",
|
|
366
366
|
"document-features",
|
|
@@ -535,9 +535,9 @@ dependencies = [
|
|
|
535
535
|
|
|
536
536
|
[[package]]
|
|
537
537
|
name = "num_cpus"
|
|
538
|
-
version = "1.
|
|
538
|
+
version = "1.17.0"
|
|
539
539
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
540
|
-
checksum = "
|
|
540
|
+
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
|
541
541
|
dependencies = [
|
|
542
542
|
"hermit-abi",
|
|
543
543
|
"libc",
|
|
@@ -603,11 +603,10 @@ dependencies = [
|
|
|
603
603
|
|
|
604
604
|
[[package]]
|
|
605
605
|
name = "pyo3"
|
|
606
|
-
version = "0.
|
|
606
|
+
version = "0.25.1"
|
|
607
607
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
608
|
-
checksum = "
|
|
608
|
+
checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
|
|
609
609
|
dependencies = [
|
|
610
|
-
"cfg-if",
|
|
611
610
|
"indoc",
|
|
612
611
|
"libc",
|
|
613
612
|
"memoffset",
|
|
@@ -621,9 +620,9 @@ dependencies = [
|
|
|
621
620
|
|
|
622
621
|
[[package]]
|
|
623
622
|
name = "pyo3-build-config"
|
|
624
|
-
version = "0.
|
|
623
|
+
version = "0.25.1"
|
|
625
624
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
626
|
-
checksum = "
|
|
625
|
+
checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
|
|
627
626
|
dependencies = [
|
|
628
627
|
"once_cell",
|
|
629
628
|
"target-lexicon",
|
|
@@ -631,9 +630,9 @@ dependencies = [
|
|
|
631
630
|
|
|
632
631
|
[[package]]
|
|
633
632
|
name = "pyo3-ffi"
|
|
634
|
-
version = "0.
|
|
633
|
+
version = "0.25.1"
|
|
635
634
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
636
|
-
checksum = "
|
|
635
|
+
checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
|
|
637
636
|
dependencies = [
|
|
638
637
|
"libc",
|
|
639
638
|
"pyo3-build-config",
|
|
@@ -641,9 +640,9 @@ dependencies = [
|
|
|
641
640
|
|
|
642
641
|
[[package]]
|
|
643
642
|
name = "pyo3-macros"
|
|
644
|
-
version = "0.
|
|
643
|
+
version = "0.25.1"
|
|
645
644
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
646
|
-
checksum = "
|
|
645
|
+
checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
|
|
647
646
|
dependencies = [
|
|
648
647
|
"proc-macro2",
|
|
649
648
|
"pyo3-macros-backend",
|
|
@@ -653,9 +652,9 @@ dependencies = [
|
|
|
653
652
|
|
|
654
653
|
[[package]]
|
|
655
654
|
name = "pyo3-macros-backend"
|
|
656
|
-
version = "0.
|
|
655
|
+
version = "0.25.1"
|
|
657
656
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
658
|
-
checksum = "
|
|
657
|
+
checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
|
|
659
658
|
dependencies = [
|
|
660
659
|
"heck",
|
|
661
660
|
"proc-macro2",
|
|
@@ -839,7 +838,7 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
|
839
838
|
[[package]]
|
|
840
839
|
name = "sparrow"
|
|
841
840
|
version = "0.1.0"
|
|
842
|
-
source = "git+https://github.com/JeroenGar/sparrow.git?rev=
|
|
841
|
+
source = "git+https://github.com/JeroenGar/sparrow.git?rev=8361c59e2d800e3492ae5e7f7f781c575bdde636#8361c59e2d800e3492ae5e7f7f781c575bdde636"
|
|
843
842
|
dependencies = [
|
|
844
843
|
"anyhow",
|
|
845
844
|
"clap",
|
|
@@ -876,9 +875,10 @@ dependencies = [
|
|
|
876
875
|
|
|
877
876
|
[[package]]
|
|
878
877
|
name = "spyrrow"
|
|
879
|
-
version = "0.
|
|
878
|
+
version = "0.7.0"
|
|
880
879
|
dependencies = [
|
|
881
880
|
"jagua-rs",
|
|
881
|
+
"num_cpus",
|
|
882
882
|
"pyo3",
|
|
883
883
|
"rand",
|
|
884
884
|
"serde",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "spyrrow"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "MIT"
|
|
6
|
+
readme = "README.md"
|
|
6
7
|
|
|
7
8
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
8
9
|
[lib]
|
|
@@ -10,9 +11,10 @@ name = "spyrrow"
|
|
|
10
11
|
crate-type = ["cdylib"]
|
|
11
12
|
|
|
12
13
|
[dependencies]
|
|
13
|
-
jagua-rs = { git = "https://github.com/JeroenGar/jagua-rs.git", rev="
|
|
14
|
-
pyo3 = "0.
|
|
14
|
+
jagua-rs = { git = "https://github.com/JeroenGar/jagua-rs.git", rev="f8f18907a6a82310769eed60fa6d8c0e7aaea0d3", features = ["spp"]}
|
|
15
|
+
pyo3 = "0.25.1"
|
|
15
16
|
rand = { version = "0.9.0", features = ["small_rng"] }
|
|
16
17
|
serde = {version = "1.0.219", features = ["derive"]}
|
|
17
18
|
serde_json = "1.0.140"
|
|
18
|
-
sparrow = { git = "https://github.com/JeroenGar/sparrow.git",rev="
|
|
19
|
+
sparrow = { git = "https://github.com/JeroenGar/sparrow.git",rev="8361c59e2d800e3492ae5e7f7f781c575bdde636",features = ["only_final_svg"]}
|
|
20
|
+
num_cpus="1.17.0"
|
spyrrow-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spyrrow
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
License-File: LICENSE.txt
|
|
8
|
+
Author-email: Paul Durand-Lupinski <paul.durand-lupinski@reeverse-systems.com>
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Project-URL: documentation, https://spyrrow.readthedocs.io/
|
|
11
|
+
Project-URL: source, https://github.com/PaulDL-RS/spyrrow
|
|
@@ -46,7 +46,8 @@ Examples
|
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
instance = spyrrow.StripPackingInstance("test", strip_height=2.001, items=[rectangle1,triangle1])
|
|
49
|
-
|
|
49
|
+
config = spyrrow.StripPackingConfig(early_termination=False,total_computation_time=60,num_wokers=3,seed=0)
|
|
50
|
+
sol = instance.solve(config)
|
|
50
51
|
print(sol.width)
|
|
51
52
|
print(sol.density)
|
|
52
53
|
print("\n")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import TypeAlias
|
|
1
|
+
from typing import TypeAlias, Optional
|
|
2
|
+
from datetime import timedelta
|
|
2
3
|
|
|
3
4
|
Point: TypeAlias = tuple[float, float]
|
|
4
5
|
|
|
@@ -67,6 +68,61 @@ class StripPackingSolution:
|
|
|
67
68
|
density: float
|
|
68
69
|
placed_items: list[PlacedItem]
|
|
69
70
|
|
|
71
|
+
class StripPackingConfig:
|
|
72
|
+
early_termination: bool
|
|
73
|
+
seed: int
|
|
74
|
+
exploration_time: timedelta
|
|
75
|
+
compression_time: timedelta
|
|
76
|
+
quadtree_depth: int
|
|
77
|
+
num_wokers:Optional[int]
|
|
78
|
+
min_items_separation: Optional[float]
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
early_termination: bool = True,
|
|
83
|
+
quadtree_depth: int = 4,
|
|
84
|
+
min_items_separation: Optional[float] = None,
|
|
85
|
+
total_computation_time: Optional[int] = 600,
|
|
86
|
+
exploration_time: Optional[int] = None,
|
|
87
|
+
compression_time: Optional[int] = None,
|
|
88
|
+
num_wokers:Optional[int]= None,
|
|
89
|
+
seed: Optional[int] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Initializes a configuration object for the strip packing algorithm.
|
|
92
|
+
|
|
93
|
+
Either `total_computation_time`, or both `exploration_time` and
|
|
94
|
+
`compression_time`, must be provided. Providing all three or only
|
|
95
|
+
one of the latter two raises an error.
|
|
96
|
+
|
|
97
|
+
If `total_computation_time` is provided, 80% of it is allocated to
|
|
98
|
+
exploration and 20% to compression.
|
|
99
|
+
|
|
100
|
+
If `seed` is not provided, a random seed will be generated.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
early_termination (bool, optional): Whether to allow early termination of the algorithm. Defaults to True.
|
|
104
|
+
quadtree_depth (int, optional): Maximum depth of the quadtree used by the collision detection engine jagua-rs.
|
|
105
|
+
Must be positive, common values are 3,4,5. Defaults to 4.
|
|
106
|
+
min_items_separation (Optional[float], optional): Minimum required distance between packed items. Defaults to None.
|
|
107
|
+
total_computation_time (Optional[int], optional): Total time budget in seconds.
|
|
108
|
+
Used if `exploration_time` and `compression_time` are not provided. Defaults to 600.
|
|
109
|
+
exploration_time (Optional[int], optional): Time in seconds allocated to exploration. Defaults to None.
|
|
110
|
+
compression_time (Optional[int], optional): Time in seconds allocated to compression. Defaults to None.
|
|
111
|
+
num_workers (Optional[int], optional): Number of threads used by the collision detection engine during exploration.
|
|
112
|
+
When set to None, detect the number of logical CPU cores on the execution plateform. Defaults to None.
|
|
113
|
+
seed (Optional[int], optional): Optional random seed to give reproductibility. If None, a random seed is generated. Defaults to None.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ValueError: If the combination of time arguments is invalid.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def to_json_str(self)->str:
|
|
120
|
+
"""Return a string of the JSON representation of the object
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str
|
|
124
|
+
"""
|
|
125
|
+
|
|
70
126
|
class StripPackingInstance:
|
|
71
127
|
name: str
|
|
72
128
|
strip_height: float
|
|
@@ -87,13 +143,12 @@ class StripPackingInstance:
|
|
|
87
143
|
def to_json_str(self) -> str:
|
|
88
144
|
"""Return a string of the JSON representation of the object"""
|
|
89
145
|
|
|
90
|
-
def solve(self,
|
|
146
|
+
def solve(self, config: StripPackingConfig) -> StripPackingSolution:
|
|
91
147
|
"""
|
|
92
148
|
The method to solve the instance.
|
|
93
149
|
|
|
94
150
|
Args:
|
|
95
|
-
|
|
96
|
-
The algorithm won't exit early.Waht you input is what you get. Default is 600 s = 10 minutes.
|
|
151
|
+
config (StripPackingConfig): the config object to precise behavior of how to solve the strip packing instance.
|
|
97
152
|
|
|
98
153
|
Returns:
|
|
99
154
|
a StripPackingSolution
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
use jagua_rs::io::ext_repr::{ExtItem as BaseItem, ExtSPolygon, ExtShape};
|
|
2
2
|
use jagua_rs::io::import::Importer;
|
|
3
3
|
use jagua_rs::probs::spp::io::ext_repr::{ExtItem, ExtSPInstance};
|
|
4
|
+
use num_cpus;
|
|
4
5
|
use pyo3::exceptions::PyValueError;
|
|
5
6
|
use pyo3::prelude::*;
|
|
6
7
|
use rand::SeedableRng;
|
|
7
8
|
use rand::prelude::SmallRng;
|
|
8
9
|
use serde::Serialize;
|
|
9
10
|
use sparrow::EPOCH;
|
|
10
|
-
use sparrow::config::{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
use sparrow::
|
|
11
|
+
use sparrow::config::{DEFAULT_SPARROW_CONFIG, ShrinkDecayStrategy};
|
|
12
|
+
use sparrow::consts::{DEFAULT_FAIL_DECAY_RATIO_CMPR, DEFAULT_MAX_CONSEQ_FAILS_EXPL};
|
|
13
|
+
use sparrow::optimizer::optimize;
|
|
14
|
+
use sparrow::util::listener::DummySolListener;
|
|
15
|
+
use sparrow::util::terminator::BasicTerminator;
|
|
14
16
|
use std::collections::HashSet;
|
|
15
|
-
|
|
17
|
+
|
|
16
18
|
use std::time::Duration;
|
|
17
19
|
|
|
18
20
|
#[pyclass(name = "Item", get_all, set_all)]
|
|
@@ -123,6 +125,100 @@ fn all_unique(strings: &[&str]) -> bool {
|
|
|
123
125
|
strings.iter().all(|s| seen.insert(*s))
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
#[pyclass(name = "StripPackingConfig", get_all, set_all)]
|
|
129
|
+
#[derive(Clone, Serialize)]
|
|
130
|
+
/// Initializes a configuration object for the strip packing algorithm.
|
|
131
|
+
///
|
|
132
|
+
/// Either `total_computation_time`, or both `exploration_time` and
|
|
133
|
+
/// `compression_time`, must be provided. Providing all three or only
|
|
134
|
+
/// one of the latter two raises an error.
|
|
135
|
+
///
|
|
136
|
+
/// If `total_computation_time` is provided, 80% of it is allocated to
|
|
137
|
+
/// exploration and 20% to compression.
|
|
138
|
+
///
|
|
139
|
+
/// If `seed` is not provided, a random seed will be generated.
|
|
140
|
+
///
|
|
141
|
+
/// Args:
|
|
142
|
+
/// early_termination (bool, optional): Whether to allow early termination of the algorithm. Defaults to True.
|
|
143
|
+
/// quadtree_depth (int, optional): Maximum depth of the quadtree used by the collision detection engine jagua-rs.
|
|
144
|
+
/// Must be positive, common values are 3,4,5. Defaults to 4.
|
|
145
|
+
/// min_items_separation (Optional[float], optional): Minimum required distance between packed items. Defaults to None.
|
|
146
|
+
/// total_computation_time (Optional[int], optional): Total time budget in seconds.
|
|
147
|
+
/// Used if `exploration_time` and `compression_time` are not provided. Defaults to 600.
|
|
148
|
+
/// exploration_time (Optional[int], optional): Time in seconds allocated to exploration. Defaults to None.
|
|
149
|
+
/// compression_time (Optional[int], optional): Time in seconds allocated to compression. Defaults to None.
|
|
150
|
+
/// num_workers (Optional[int], optional): Number of threads used by the collision detection engine during exploration.
|
|
151
|
+
/// When set to None, detect the number of logical CPU cores on the execution plateform. Defaults to None.
|
|
152
|
+
/// seed (Optional[int], optional): Optional random seed to give reproductibility. If None, a random seed is generated. Defaults to None.
|
|
153
|
+
///
|
|
154
|
+
/// Raises:
|
|
155
|
+
/// ValueError: If the combination of time arguments is invalid.
|
|
156
|
+
struct StripPackingConfigPy {
|
|
157
|
+
early_termination: bool,
|
|
158
|
+
seed: u64,
|
|
159
|
+
exploration_time: Duration,
|
|
160
|
+
compression_time: Duration,
|
|
161
|
+
quadtree_depth: u8,
|
|
162
|
+
min_items_separation: Option<f32>,
|
|
163
|
+
num_wokers: usize,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[pymethods]
|
|
167
|
+
impl StripPackingConfigPy {
|
|
168
|
+
#[new]
|
|
169
|
+
#[pyo3(signature = (early_termination=true,quadtree_depth=4,min_items_separation=None,total_computation_time=600,exploration_time=None,compression_time=None,num_wokers=None,seed=None))]
|
|
170
|
+
fn new(
|
|
171
|
+
early_termination: bool,
|
|
172
|
+
quadtree_depth: u8,
|
|
173
|
+
min_items_separation: Option<f32>,
|
|
174
|
+
total_computation_time: Option<u64>,
|
|
175
|
+
exploration_time: Option<u64>,
|
|
176
|
+
compression_time: Option<u64>,
|
|
177
|
+
num_wokers: Option<usize>,
|
|
178
|
+
seed: Option<u64>,
|
|
179
|
+
) -> PyResult<Self> {
|
|
180
|
+
let (exploration_time, compression_time) = match (
|
|
181
|
+
total_computation_time,
|
|
182
|
+
exploration_time,
|
|
183
|
+
compression_time,
|
|
184
|
+
) {
|
|
185
|
+
(None, Some(exploration_time), Some(compression_time)) => (
|
|
186
|
+
Duration::from_secs(exploration_time),
|
|
187
|
+
Duration::from_secs(compression_time),
|
|
188
|
+
),
|
|
189
|
+
(Some(total_computation_time), None, None) => (
|
|
190
|
+
Duration::from_secs(total_computation_time).mul_f32(0.8),
|
|
191
|
+
Duration::from_secs(total_computation_time).mul_f32(0.2),
|
|
192
|
+
),
|
|
193
|
+
_ => {
|
|
194
|
+
return Err(PyValueError::new_err(
|
|
195
|
+
"Either total_computation_time or both exploration_time and compression_time should be provided, not all 3 or some other combination",
|
|
196
|
+
));
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
let seed = seed.unwrap_or_else(rand::random);
|
|
200
|
+
let num_wokers = num_wokers.unwrap_or_else(num_cpus::get);
|
|
201
|
+
Ok(Self {
|
|
202
|
+
early_termination,
|
|
203
|
+
seed,
|
|
204
|
+
exploration_time,
|
|
205
|
+
compression_time,
|
|
206
|
+
quadtree_depth,
|
|
207
|
+
num_wokers,
|
|
208
|
+
min_items_separation,
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Return a string of the JSON representation of the object
|
|
213
|
+
///
|
|
214
|
+
/// Returns:
|
|
215
|
+
/// str
|
|
216
|
+
///
|
|
217
|
+
fn to_json_str(&self) -> String {
|
|
218
|
+
serde_json::to_string(&self).unwrap()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
126
222
|
#[pyclass(name = "StripPackingInstance", get_all, set_all)]
|
|
127
223
|
#[derive(Clone, Serialize)]
|
|
128
224
|
/// An Instance of a Strip Packing Problem.
|
|
@@ -177,7 +273,7 @@ impl StripPackingInstancePy {
|
|
|
177
273
|
fn new(name: String, strip_height: f32, items: Vec<ItemPy>) -> PyResult<Self> {
|
|
178
274
|
let item_ids: Vec<&str> = items.iter().map(|i| i.id.as_str()).collect();
|
|
179
275
|
if !all_unique(&item_ids) {
|
|
180
|
-
let error_string = format!("The item ids are not uniques: {:#?}"
|
|
276
|
+
let error_string = format!("The item ids are not uniques: {item_ids:#?}");
|
|
181
277
|
return Err(PyValueError::new_err(error_string));
|
|
182
278
|
}
|
|
183
279
|
Ok(StripPackingInstancePy {
|
|
@@ -196,7 +292,6 @@ impl StripPackingInstancePy {
|
|
|
196
292
|
serde_json::to_string(&self).unwrap()
|
|
197
293
|
}
|
|
198
294
|
|
|
199
|
-
#[pyo3(signature = (computation_time=600))]
|
|
200
295
|
/// The method to solve the instance.
|
|
201
296
|
///
|
|
202
297
|
/// Args:
|
|
@@ -206,37 +301,42 @@ impl StripPackingInstancePy {
|
|
|
206
301
|
/// Returns:
|
|
207
302
|
/// a StripPackingSolution
|
|
208
303
|
///
|
|
209
|
-
fn solve(&self,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
Duration::from_secs(computation_time).mul_f32(COMPRESS_TIME_RATIO),
|
|
224
|
-
);
|
|
304
|
+
fn solve(&self, config_py: StripPackingConfigPy, py: Python) -> StripPackingSolutionPy {
|
|
305
|
+
let mut config = DEFAULT_SPARROW_CONFIG;
|
|
306
|
+
config.rng_seed = Some(config_py.seed as usize);
|
|
307
|
+
config.expl_cfg.time_limit = config_py.exploration_time;
|
|
308
|
+
config.expl_cfg.separator_config.n_workers = config_py.num_wokers;
|
|
309
|
+
config.cmpr_cfg.time_limit = config_py.compression_time;
|
|
310
|
+
let rng = SmallRng::seed_from_u64(config_py.seed);
|
|
311
|
+
if config_py.early_termination {
|
|
312
|
+
config.expl_cfg.max_conseq_failed_attempts = Some(DEFAULT_MAX_CONSEQ_FAILS_EXPL);
|
|
313
|
+
config.cmpr_cfg.shrink_decay =
|
|
314
|
+
ShrinkDecayStrategy::FailureBased(DEFAULT_FAIL_DECAY_RATIO_CMPR);
|
|
315
|
+
}
|
|
316
|
+
config.cde_config.quadtree_depth = config_py.quadtree_depth;
|
|
317
|
+
config.min_item_separation = config_py.min_items_separation;
|
|
225
318
|
|
|
226
319
|
let ext_instance = self.clone().into();
|
|
227
|
-
let importer = Importer::new(
|
|
320
|
+
let importer = Importer::new(
|
|
321
|
+
config.cde_config,
|
|
322
|
+
config.poly_simpl_tolerance,
|
|
323
|
+
config.min_item_separation,
|
|
324
|
+
);
|
|
228
325
|
let instance = jagua_rs::probs::spp::io::import(&importer, &ext_instance)
|
|
229
326
|
.expect("Expected a Strip Packing Problem Instance");
|
|
327
|
+
let mut terminator = BasicTerminator::new();
|
|
328
|
+
|
|
329
|
+
// The Python code is not concerned with intermediary solution for now
|
|
330
|
+
let mut dummy_exporter = DummySolListener {};
|
|
230
331
|
|
|
231
332
|
py.allow_threads(move || {
|
|
232
|
-
let terminator = Terminator::new_without_ctrlc();
|
|
233
333
|
let solution = optimize(
|
|
234
334
|
instance.clone(),
|
|
235
335
|
rng,
|
|
236
|
-
|
|
237
|
-
terminator,
|
|
238
|
-
|
|
239
|
-
|
|
336
|
+
&mut dummy_exporter,
|
|
337
|
+
&mut terminator,
|
|
338
|
+
&config.expl_cfg,
|
|
339
|
+
&config.cmpr_cfg,
|
|
240
340
|
);
|
|
241
341
|
|
|
242
342
|
let solution = jagua_rs::probs::spp::io::export(&instance, &solution, *EPOCH);
|
|
@@ -251,7 +351,7 @@ impl StripPackingInstancePy {
|
|
|
251
351
|
translation: jpi.transformation.translation,
|
|
252
352
|
})
|
|
253
353
|
.collect();
|
|
254
|
-
|
|
354
|
+
|
|
255
355
|
StripPackingSolutionPy {
|
|
256
356
|
width: solution.strip_width,
|
|
257
357
|
density: solution.density,
|
|
@@ -267,6 +367,7 @@ fn spyrrow(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
267
367
|
m.add_class::<ItemPy>()?;
|
|
268
368
|
m.add_class::<PlacedItemPy>()?;
|
|
269
369
|
m.add_class::<StripPackingInstancePy>()?;
|
|
370
|
+
m.add_class::<StripPackingConfigPy>()?;
|
|
270
371
|
m.add_class::<StripPackingSolutionPy>()?;
|
|
271
372
|
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
|
272
373
|
Ok(())
|
|
@@ -15,11 +15,30 @@ def test_basic():
|
|
|
15
15
|
instance = spyrrow.StripPackingInstance(
|
|
16
16
|
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
17
17
|
)
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
config = spyrrow.StripPackingConfig(early_termination=False,total_computation_time=60,num_wokers=3,seed=0)
|
|
19
|
+
sol = instance.solve(config)
|
|
20
|
+
assert sol.width == pytest.approx(4,rel=0.05)
|
|
21
|
+
|
|
22
|
+
def test_early_termination():
|
|
23
|
+
rectangle1 = spyrrow.Item(
|
|
24
|
+
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
25
|
+
)
|
|
26
|
+
triangle1 = spyrrow.Item(
|
|
27
|
+
"triangle",
|
|
28
|
+
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
29
|
+
demand=6,
|
|
30
|
+
allowed_orientations=[0, 90, 180, -90],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
instance = spyrrow.StripPackingInstance(
|
|
34
|
+
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
35
|
+
)
|
|
36
|
+
config = spyrrow.StripPackingConfig(early_termination=True,total_computation_time=600,num_wokers=3,seed=0)
|
|
37
|
+
sol = instance.solve(config)
|
|
38
|
+
assert sol.width == pytest.approx(4,rel=0.05)
|
|
20
39
|
|
|
21
40
|
def test_2_consecutive_calls():
|
|
22
|
-
# Test
|
|
41
|
+
# Test corresponding to crash on the second consecutive call of solve method
|
|
23
42
|
rectangle1 = spyrrow.Item(
|
|
24
43
|
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
25
44
|
)
|
|
@@ -33,9 +52,11 @@ def test_2_consecutive_calls():
|
|
|
33
52
|
instance = spyrrow.StripPackingInstance(
|
|
34
53
|
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
35
54
|
)
|
|
36
|
-
|
|
37
|
-
sol = instance.solve(
|
|
38
|
-
|
|
55
|
+
config = spyrrow.StripPackingConfig(early_termination=False,total_computation_time=10,seed=0)
|
|
56
|
+
sol = instance.solve(config)
|
|
57
|
+
config = spyrrow.StripPackingConfig(early_termination=False,total_computation_time=30,seed=0)
|
|
58
|
+
sol = instance.solve(config)
|
|
59
|
+
assert sol.width == pytest.approx(4,rel=0.05)
|
|
39
60
|
|
|
40
61
|
def test_concave_polygons():
|
|
41
62
|
poly1 = spyrrow.Item("0",[(0, 0), (3, 0), (4, 1), (3, 2), (0, 2), (1, 1), (0, 0)],demand=2,allowed_orientations=[0,90,180,270])
|
|
@@ -43,7 +64,8 @@ def test_concave_polygons():
|
|
|
43
64
|
instance = spyrrow.StripPackingInstance(
|
|
44
65
|
"test", strip_height=4.001, items=[poly1, poly2]
|
|
45
66
|
)
|
|
46
|
-
|
|
67
|
+
config = spyrrow.StripPackingConfig(early_termination=True,total_computation_time=30,seed=0)
|
|
68
|
+
sol = instance.solve(config)
|
|
47
69
|
|
|
48
70
|
def test_continuous_rotation():
|
|
49
71
|
rectangle1 = spyrrow.Item(
|
|
@@ -59,5 +81,11 @@ def test_continuous_rotation():
|
|
|
59
81
|
instance = spyrrow.StripPackingInstance(
|
|
60
82
|
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
61
83
|
)
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
config = spyrrow.StripPackingConfig(early_termination=True,total_computation_time=90,seed=0)
|
|
85
|
+
sol = instance.solve(config)
|
|
86
|
+
print(sol.width)
|
|
87
|
+
assert sol.width >= 3.5
|
|
88
|
+
assert sol.width < 4
|
|
89
|
+
|
|
90
|
+
if __name__ == '__main__':
|
|
91
|
+
test_continuous_rotation()
|
spyrrow-0.6.0/PKG-INFO
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: spyrrow
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Classifier: Programming Language :: Rust
|
|
5
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
-
License-File: LICENSE.txt
|
|
8
|
-
Author-email: Paul Durand-Lupinski <paul.durand-lupinski@reeverse-systems.com>
|
|
9
|
-
License: MIT
|
|
10
|
-
Requires-Python: >=3.10
|
|
11
|
-
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
12
|
-
Project-URL: documentation, https://spyrrow.readthedocs.io/
|
|
13
|
-
Project-URL: source, https://github.com/PaulDL-RS/spyrrow
|
|
14
|
-
|
|
15
|
-
# Spyrrow
|
|
16
|
-
|
|
17
|
-
`spyrrow` is a Python wrapper on the Rust project [`sparrow`](https://github.com/JeroenGar/sparrow).
|
|
18
|
-
It enables to solve 2D [Strip packing problems](https://en.wikipedia.org/wiki/Strip_packing_problem).
|
|
19
|
-
|
|
20
|
-
The documentation is hosted [here](https://spyrrow.readthedocs.io/).
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
Spyrrow is hosted on [PyPI](https://pypi.org/project/spyrrow/).
|
|
25
|
-
|
|
26
|
-
You can install with the package manager of your choice, using the PyPI package index.
|
|
27
|
-
|
|
28
|
-
For example, with `pip`, the default Python package:
|
|
29
|
-
```bash
|
|
30
|
-
pip install spyrrow
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Examples
|
|
34
|
-
```python
|
|
35
|
-
import spyrrow
|
|
36
|
-
|
|
37
|
-
rectangle1 = spyrrow.Item(
|
|
38
|
-
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
39
|
-
)
|
|
40
|
-
triangle1 = spyrrow.Item(
|
|
41
|
-
"triangle",
|
|
42
|
-
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
43
|
-
demand=6,
|
|
44
|
-
allowed_orientations=[0, 90, 180, -90],
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
instance = spyrrow.StripPackingInstance("test", strip_height=2.001, items=[rectangle1,triangle1])
|
|
48
|
-
sol:spyrrow.StripPackingSolution = instance.solve(30)
|
|
49
|
-
print(sol.width)
|
|
50
|
-
print(sol.density)
|
|
51
|
-
print("\n")
|
|
52
|
-
for pi in sol.placed_items:
|
|
53
|
-
print(pi.id)
|
|
54
|
-
print(pi.rotation)
|
|
55
|
-
print(pi.translation)
|
|
56
|
-
print("\n")
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Contributing
|
|
60
|
-
|
|
61
|
-
Spyrrow is open to contributions.
|
|
62
|
-
The first target should be to reach Python open sources packages standards and practices.
|
|
63
|
-
Second, a easier integration with the package `shapely` is envsionned.
|
|
64
|
-
|
|
65
|
-
Please use GitHub issues to request features.
|
|
66
|
-
They will be considered relative to what is already implemented in the parent library `sparrow`.
|
|
67
|
-
If necessary, they can be forwarded to it.
|
|
68
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|