spyrrow 0.5.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.5.0 → spyrrow-0.7.0}/Cargo.lock +20 -19
- {spyrrow-0.5.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.5.0 → spyrrow-0.7.0}/docs/api.rst +2 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/docs/index.rst +16 -2
- spyrrow-0.7.0/spyrrow.pyi +155 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/src/lib.rs +136 -36
- spyrrow-0.7.0/tests/test_basic.py +91 -0
- spyrrow-0.5.0/PKG-INFO +0 -68
- spyrrow-0.5.0/spyrrow.pyi +0 -102
- spyrrow-0.5.0/tests/test_basic.py +0 -47
- {spyrrow-0.5.0 → spyrrow-0.7.0}/.github/workflows/CI.yml +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/.gitignore +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/.readthedocs.yaml +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/LICENSE.txt +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/README.md +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/docs/Makefile +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/docs/conf.py +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/docs/make.bat +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/pyproject.toml +0 -0
- {spyrrow-0.5.0 → spyrrow-0.7.0}/tests/utils.py +0 -0
- {spyrrow-0.5.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",
|
|
@@ -850,6 +849,7 @@ dependencies = [
|
|
|
850
849
|
"jagua-rs",
|
|
851
850
|
"jiff",
|
|
852
851
|
"log",
|
|
852
|
+
"ndarray",
|
|
853
853
|
"num_cpus",
|
|
854
854
|
"numfmt",
|
|
855
855
|
"ordered-float",
|
|
@@ -875,9 +875,10 @@ dependencies = [
|
|
|
875
875
|
|
|
876
876
|
[[package]]
|
|
877
877
|
name = "spyrrow"
|
|
878
|
-
version = "0.
|
|
878
|
+
version = "0.7.0"
|
|
879
879
|
dependencies = [
|
|
880
880
|
"jagua-rs",
|
|
881
|
+
"num_cpus",
|
|
881
882
|
"pyo3",
|
|
882
883
|
"rand",
|
|
883
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")
|
|
@@ -54,4 +55,17 @@ Examples
|
|
|
54
55
|
print(pi.id)
|
|
55
56
|
print(pi.rotation)
|
|
56
57
|
print(pi.translation)
|
|
57
|
-
print("\n")
|
|
58
|
+
print("\n")
|
|
59
|
+
|
|
60
|
+
In order to express that an Item can rotate freely, its `allowed_orientations` attributes should be set to `None`.
|
|
61
|
+
|
|
62
|
+
.. code-block:: python
|
|
63
|
+
|
|
64
|
+
import spyrrow
|
|
65
|
+
|
|
66
|
+
triangle1 = spyrrow.Item(
|
|
67
|
+
"triangle",
|
|
68
|
+
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
69
|
+
demand=6,
|
|
70
|
+
allowed_orientations=None,
|
|
71
|
+
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from typing import TypeAlias, Optional
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
Point: TypeAlias = tuple[float, float]
|
|
5
|
+
|
|
6
|
+
class Item:
|
|
7
|
+
id: str
|
|
8
|
+
demand: int
|
|
9
|
+
shape: list[Point]
|
|
10
|
+
allowed_orientations: list[float]
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
id: str,
|
|
15
|
+
shape: list[Point],
|
|
16
|
+
demand: int,
|
|
17
|
+
allowed_orientations: list[float] | None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
An Item represents any closed 2D shape by its outer boundary.
|
|
21
|
+
|
|
22
|
+
Spyrrow doesn't support hole(s) inside the shape as of yet. Therefore no Item can be nested inside another.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
id (str): The Item identifier
|
|
26
|
+
Needs to be unique accross all Items of a StripPackingInstance
|
|
27
|
+
shape: An ordered list of (x,y) defining the shape boundary. The shape is represented as a polygon formed by this list of points.
|
|
28
|
+
The origin point can be included twice as the finishing point. If not, [last point, first point] is infered to be the last straight line of the shape.
|
|
29
|
+
demand: The quantity of identical Items to be placed inside the strip. Should be positive.
|
|
30
|
+
allowed_orientations (list[float]|None): List of angles in degrees allowed.
|
|
31
|
+
An empty list is equivalent to [0.].
|
|
32
|
+
A None value means that the item is free to rotate
|
|
33
|
+
The algorithmn is only very weakly sensible to the length of the list given.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def to_json_str(self) -> str:
|
|
38
|
+
"""Return a string of the JSON representation of the object"""
|
|
39
|
+
|
|
40
|
+
class PlacedItem:
|
|
41
|
+
"""
|
|
42
|
+
An object representing where a copy of an Item was placed inside the strip.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
id (str): The Item identifier referencing the items of the StripPackingInstance
|
|
46
|
+
rotation (float): The rotation angle in degrees, assuming that the original Item was defined with 0° as its rotation angle.
|
|
47
|
+
Use the origin (0.0,0.0) as the rotation point.
|
|
48
|
+
translation (tuple[float,float]): the translation vector in the X-Y axis. To apply after the rotation
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
id: str
|
|
52
|
+
translation: Point
|
|
53
|
+
rotation: float
|
|
54
|
+
|
|
55
|
+
class StripPackingSolution:
|
|
56
|
+
"""
|
|
57
|
+
An object representing the solution to a given StripPackingInstance.
|
|
58
|
+
|
|
59
|
+
Can not be directly instanciated. Result from StripPackingInstance.solve.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
width (float): the width of the strip found to contains all Items. In the same unit as input.
|
|
63
|
+
placed_items (list[PlacedItem]): a list of all PlacedItems, describing how Items are placed in the solution
|
|
64
|
+
density (float): the fraction of the final strip used by items.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
width: float
|
|
68
|
+
density: float
|
|
69
|
+
placed_items: list[PlacedItem]
|
|
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
|
+
|
|
126
|
+
class StripPackingInstance:
|
|
127
|
+
name: str
|
|
128
|
+
strip_height: float
|
|
129
|
+
items: list[Item]
|
|
130
|
+
|
|
131
|
+
def __init__(self, name: str, strip_height: float, items: list[Item]):
|
|
132
|
+
"""
|
|
133
|
+
An Instance of a Strip Packing Problem.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
name (str): The name of the instance. Required by the underlying sparrow library.
|
|
137
|
+
An empty string '' can be used, if the user doesn't have a use for this name.
|
|
138
|
+
strip_height (float): the fixed height of the strip. The unit should be compatible with the Item
|
|
139
|
+
items (list[Item]): The Items which defines the instances. All Items should be defined with the same scale ( same length unit).
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError
|
|
142
|
+
"""
|
|
143
|
+
def to_json_str(self) -> str:
|
|
144
|
+
"""Return a string of the JSON representation of the object"""
|
|
145
|
+
|
|
146
|
+
def solve(self, config: StripPackingConfig) -> StripPackingSolution:
|
|
147
|
+
"""
|
|
148
|
+
The method to solve the instance.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
config (StripPackingConfig): the config object to precise behavior of how to solve the strip packing instance.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
a StripPackingSolution
|
|
155
|
+
"""
|
|
@@ -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)]
|
|
@@ -21,8 +23,6 @@ use std::time::Duration;
|
|
|
21
23
|
///
|
|
22
24
|
/// Spyrrow doesn't support hole(s) inside the shape as of yet. Therefore no Item can be nested inside another.
|
|
23
25
|
///
|
|
24
|
-
/// Continous rotation is not supported as of yet. A workaround is to specify any integer degrees between 0 and 360
|
|
25
|
-
/// to the allowed_orientations list.
|
|
26
26
|
///
|
|
27
27
|
/// Args:
|
|
28
28
|
/// id (str): The Item identifier
|
|
@@ -30,7 +30,9 @@ use std::time::Duration;
|
|
|
30
30
|
/// shape (list[tuple[float,float]]): An ordered list of (x,y) defining the shape boundary. The shape is represented as a polygon formed by this list of points.
|
|
31
31
|
/// The origin point can be included twice as the finishing point. If not, [last point, first point] is infered to be the last straight line of the shape.
|
|
32
32
|
/// demand (int): The quantity of identical Items to be placed inside the strip. Should be positive.
|
|
33
|
-
/// allowed_orientations (list[float]): List of angles in degrees allowed.
|
|
33
|
+
/// allowed_orientations (list[float]|None): List of angles in degrees allowed.
|
|
34
|
+
/// An empty list is equivalent to [0.].
|
|
35
|
+
/// A None value means that the item is free to rotate
|
|
34
36
|
/// The algorithmn is only very weakly sensible to the length of the list given.
|
|
35
37
|
///
|
|
36
38
|
struct ItemPy {
|
|
@@ -47,12 +49,12 @@ impl ItemPy {
|
|
|
47
49
|
id: String,
|
|
48
50
|
shape: Vec<(f32, f32)>,
|
|
49
51
|
demand: u64,
|
|
50
|
-
allowed_orientations: Vec<f32
|
|
52
|
+
allowed_orientations: Option<Vec<f32>>,
|
|
51
53
|
) -> Self {
|
|
52
54
|
ItemPy {
|
|
53
55
|
id,
|
|
54
56
|
demand,
|
|
55
|
-
allowed_orientations
|
|
57
|
+
allowed_orientations,
|
|
56
58
|
shape,
|
|
57
59
|
}
|
|
58
60
|
}
|
|
@@ -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.
|
|
@@ -160,7 +256,6 @@ impl From<StripPackingInstancePy> for ExtSPInstance {
|
|
|
160
256
|
ExtItem {
|
|
161
257
|
base,
|
|
162
258
|
demand: v.demand,
|
|
163
|
-
// }
|
|
164
259
|
}
|
|
165
260
|
})
|
|
166
261
|
.collect();
|
|
@@ -178,7 +273,7 @@ impl StripPackingInstancePy {
|
|
|
178
273
|
fn new(name: String, strip_height: f32, items: Vec<ItemPy>) -> PyResult<Self> {
|
|
179
274
|
let item_ids: Vec<&str> = items.iter().map(|i| i.id.as_str()).collect();
|
|
180
275
|
if !all_unique(&item_ids) {
|
|
181
|
-
let error_string = format!("The item ids are not uniques: {:#?}"
|
|
276
|
+
let error_string = format!("The item ids are not uniques: {item_ids:#?}");
|
|
182
277
|
return Err(PyValueError::new_err(error_string));
|
|
183
278
|
}
|
|
184
279
|
Ok(StripPackingInstancePy {
|
|
@@ -197,7 +292,6 @@ impl StripPackingInstancePy {
|
|
|
197
292
|
serde_json::to_string(&self).unwrap()
|
|
198
293
|
}
|
|
199
294
|
|
|
200
|
-
#[pyo3(signature = (computation_time=600))]
|
|
201
295
|
/// The method to solve the instance.
|
|
202
296
|
///
|
|
203
297
|
/// Args:
|
|
@@ -207,37 +301,42 @@ impl StripPackingInstancePy {
|
|
|
207
301
|
/// Returns:
|
|
208
302
|
/// a StripPackingSolution
|
|
209
303
|
///
|
|
210
|
-
fn solve(&self,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Duration::from_secs(computation_time).mul_f32(COMPRESS_TIME_RATIO),
|
|
225
|
-
);
|
|
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;
|
|
226
318
|
|
|
227
319
|
let ext_instance = self.clone().into();
|
|
228
|
-
let importer = Importer::new(
|
|
320
|
+
let importer = Importer::new(
|
|
321
|
+
config.cde_config,
|
|
322
|
+
config.poly_simpl_tolerance,
|
|
323
|
+
config.min_item_separation,
|
|
324
|
+
);
|
|
229
325
|
let instance = jagua_rs::probs::spp::io::import(&importer, &ext_instance)
|
|
230
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 {};
|
|
231
331
|
|
|
232
332
|
py.allow_threads(move || {
|
|
233
|
-
let terminator = Terminator::new_without_ctrlc();
|
|
234
333
|
let solution = optimize(
|
|
235
334
|
instance.clone(),
|
|
236
335
|
rng,
|
|
237
|
-
|
|
238
|
-
terminator,
|
|
239
|
-
|
|
240
|
-
|
|
336
|
+
&mut dummy_exporter,
|
|
337
|
+
&mut terminator,
|
|
338
|
+
&config.expl_cfg,
|
|
339
|
+
&config.cmpr_cfg,
|
|
241
340
|
);
|
|
242
341
|
|
|
243
342
|
let solution = jagua_rs::probs::spp::io::export(&instance, &solution, *EPOCH);
|
|
@@ -252,7 +351,7 @@ impl StripPackingInstancePy {
|
|
|
252
351
|
translation: jpi.transformation.translation,
|
|
253
352
|
})
|
|
254
353
|
.collect();
|
|
255
|
-
|
|
354
|
+
|
|
256
355
|
StripPackingSolutionPy {
|
|
257
356
|
width: solution.strip_width,
|
|
258
357
|
density: solution.density,
|
|
@@ -268,6 +367,7 @@ fn spyrrow(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
268
367
|
m.add_class::<ItemPy>()?;
|
|
269
368
|
m.add_class::<PlacedItemPy>()?;
|
|
270
369
|
m.add_class::<StripPackingInstancePy>()?;
|
|
370
|
+
m.add_class::<StripPackingConfigPy>()?;
|
|
271
371
|
m.add_class::<StripPackingSolutionPy>()?;
|
|
272
372
|
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
|
273
373
|
Ok(())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import spyrrow
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
def test_basic():
|
|
5
|
+
rectangle1 = spyrrow.Item(
|
|
6
|
+
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
7
|
+
)
|
|
8
|
+
triangle1 = spyrrow.Item(
|
|
9
|
+
"triangle",
|
|
10
|
+
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
11
|
+
demand=6,
|
|
12
|
+
allowed_orientations=[0, 90, 180, -90],
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
instance = spyrrow.StripPackingInstance(
|
|
16
|
+
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
17
|
+
)
|
|
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)
|
|
39
|
+
|
|
40
|
+
def test_2_consecutive_calls():
|
|
41
|
+
# Test corresponding to crash on the second consecutive call of solve method
|
|
42
|
+
rectangle1 = spyrrow.Item(
|
|
43
|
+
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
44
|
+
)
|
|
45
|
+
triangle1 = spyrrow.Item(
|
|
46
|
+
"triangle",
|
|
47
|
+
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
48
|
+
demand=6,
|
|
49
|
+
allowed_orientations=[0, 90, 180, -90],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
instance = spyrrow.StripPackingInstance(
|
|
53
|
+
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
54
|
+
)
|
|
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)
|
|
60
|
+
|
|
61
|
+
def test_concave_polygons():
|
|
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])
|
|
63
|
+
poly2 = spyrrow.Item("1",[(0, 0), (1, 0), (1, 2), (3, 2), (3, 0), (4, 0), (4, 3), (0, 3), (0, 0)], demand=3, allowed_orientations=[0,90,180,270])
|
|
64
|
+
instance = spyrrow.StripPackingInstance(
|
|
65
|
+
"test", strip_height=4.001, items=[poly1, poly2]
|
|
66
|
+
)
|
|
67
|
+
config = spyrrow.StripPackingConfig(early_termination=True,total_computation_time=30,seed=0)
|
|
68
|
+
sol = instance.solve(config)
|
|
69
|
+
|
|
70
|
+
def test_continuous_rotation():
|
|
71
|
+
rectangle1 = spyrrow.Item(
|
|
72
|
+
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=None
|
|
73
|
+
)
|
|
74
|
+
triangle1 = spyrrow.Item(
|
|
75
|
+
"triangle",
|
|
76
|
+
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
77
|
+
demand=6,
|
|
78
|
+
allowed_orientations=None,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
instance = spyrrow.StripPackingInstance(
|
|
82
|
+
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
83
|
+
)
|
|
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.5.0/PKG-INFO
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: spyrrow
|
|
3
|
-
Version: 0.5.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
|
-
|
spyrrow-0.5.0/spyrrow.pyi
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
from typing import TypeAlias
|
|
2
|
-
|
|
3
|
-
Point: TypeAlias = tuple[float, float]
|
|
4
|
-
|
|
5
|
-
class Item:
|
|
6
|
-
id: str
|
|
7
|
-
demand: int
|
|
8
|
-
shape: list[Point]
|
|
9
|
-
allowed_orientations: list[float]
|
|
10
|
-
|
|
11
|
-
def __init__(
|
|
12
|
-
self,
|
|
13
|
-
id: str,
|
|
14
|
-
shape: list[Point],
|
|
15
|
-
demand: int,
|
|
16
|
-
allowed_orientations: list[float],
|
|
17
|
-
):
|
|
18
|
-
"""
|
|
19
|
-
An Item represents any closed 2D shape by its outer boundary.
|
|
20
|
-
|
|
21
|
-
Spyrrow doesn't support hole(s) inside the shape as of yet. Therefore no Item can be nested inside another.
|
|
22
|
-
|
|
23
|
-
Continous rotation is not supported as of yet. A workaround is to specify any integer degrees between 0 and 360
|
|
24
|
-
to the allowed_orientations list.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
id (str): The Item identifier
|
|
28
|
-
Needs to be unique accross all Items of a StripPackingInstance
|
|
29
|
-
shape: An ordered list of (x,y) defining the shape boundary. The shape is represented as a polygon formed by this list of points.
|
|
30
|
-
The origin point can be included twice as the finishing point. If not, [last point, first point] is infered to be the last straight line of the shape.
|
|
31
|
-
demand: The quantity of identical Items to be placed inside the strip. Should be positive.
|
|
32
|
-
allowed_orientations: List of angles in degrees allowed. An empty list is equivalent to [0.].
|
|
33
|
-
The algorithmn is only very weakly sensible to the length of the list given.
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def to_json_str(self)->str:
|
|
38
|
-
""" Return a string of the JSON representation of the object"""
|
|
39
|
-
|
|
40
|
-
class PlacedItem:
|
|
41
|
-
"""
|
|
42
|
-
An object representing where a copy of an Item was placed inside the strip.
|
|
43
|
-
|
|
44
|
-
Attributes:
|
|
45
|
-
id (str): The Item identifier referencing the items of the StripPackingInstance
|
|
46
|
-
rotation (float): The rotation angle in degrees, assuming that the original Item was defined with 0° as its rotation angle.
|
|
47
|
-
Use the origin (0.0,0.0) as the rotation point.
|
|
48
|
-
translation (tuple[float,float]): the translation vector in the X-Y axis. To apply after the rotation
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
id: str
|
|
52
|
-
translation: Point
|
|
53
|
-
rotation: float
|
|
54
|
-
|
|
55
|
-
class StripPackingSolution:
|
|
56
|
-
"""
|
|
57
|
-
An object representing the solution to a given StripPackingInstance.
|
|
58
|
-
|
|
59
|
-
Can not be directly instanciated. Result from StripPackingInstance.solve.
|
|
60
|
-
|
|
61
|
-
Attributes:
|
|
62
|
-
width (float): the width of the strip found to contains all Items. In the same unit as input.
|
|
63
|
-
placed_items (list[PlacedItem]): a list of all PlacedItems, describing how Items are placed in the solution
|
|
64
|
-
density (float): the fraction of the final strip used by items.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
width: float
|
|
68
|
-
density: float
|
|
69
|
-
placed_items: list[PlacedItem]
|
|
70
|
-
|
|
71
|
-
class StripPackingInstance:
|
|
72
|
-
name: str
|
|
73
|
-
strip_height: float
|
|
74
|
-
items: list[Item]
|
|
75
|
-
|
|
76
|
-
def __init__(self, name: str, strip_height: float, items: list[Item]):
|
|
77
|
-
"""
|
|
78
|
-
An Instance of a Strip Packing Problem.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
name (str): The name of the instance. Required by the underlying sparrow library.
|
|
82
|
-
An empty string '' can be used, if the user doesn't have a use for this name.
|
|
83
|
-
strip_height (float): the fixed height of the strip. The unit should be compatible with the Item
|
|
84
|
-
items (list[Item]): The Items which defines the instances. All Items should be defined with the same scale ( same length unit).
|
|
85
|
-
Raises:
|
|
86
|
-
ValueError
|
|
87
|
-
"""
|
|
88
|
-
def to_json_str(self)->str:
|
|
89
|
-
""" Return a string of the JSON representation of the object"""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def solve(self, computation_time: int = 600) -> StripPackingSolution:
|
|
93
|
-
"""
|
|
94
|
-
The method to solve the instance.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
computation_time (int): The total computation time in seconds used to find a solution.
|
|
98
|
-
The algorithm won't exit early.Waht you input is what you get. Default is 600 s = 10 minutes.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
a StripPackingSolution
|
|
102
|
-
"""
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import spyrrow
|
|
2
|
-
import pytest
|
|
3
|
-
|
|
4
|
-
def test_basic():
|
|
5
|
-
## Continuous rotation seems to not be implemented for strip packing
|
|
6
|
-
rectangle1 = spyrrow.Item(
|
|
7
|
-
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
8
|
-
)
|
|
9
|
-
triangle1 = spyrrow.Item(
|
|
10
|
-
"triangle",
|
|
11
|
-
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
12
|
-
demand=6,
|
|
13
|
-
allowed_orientations=[0, 90, 180, -90],
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
instance = spyrrow.StripPackingInstance(
|
|
17
|
-
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
18
|
-
)
|
|
19
|
-
sol = instance.solve(30)
|
|
20
|
-
assert sol.width == pytest.approx(4,rel=0.2)
|
|
21
|
-
|
|
22
|
-
def test_2_consecutive_calls():
|
|
23
|
-
# Test correpsonding to crash on the second consecutive call of solve method
|
|
24
|
-
rectangle1 = spyrrow.Item(
|
|
25
|
-
"rectangle", [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)], demand=4, allowed_orientations=[0]
|
|
26
|
-
)
|
|
27
|
-
triangle1 = spyrrow.Item(
|
|
28
|
-
"triangle",
|
|
29
|
-
[(0, 0), (1, 0), (1, 1), (0, 0)],
|
|
30
|
-
demand=6,
|
|
31
|
-
allowed_orientations=[0, 90, 180, -90],
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
instance = spyrrow.StripPackingInstance(
|
|
35
|
-
"test", strip_height=2.001, items=[rectangle1, triangle1]
|
|
36
|
-
)
|
|
37
|
-
sol = instance.solve(10)
|
|
38
|
-
sol = instance.solve(30)
|
|
39
|
-
assert sol.width == pytest.approx(4,rel=0.2)
|
|
40
|
-
|
|
41
|
-
def test_concave_polygons():
|
|
42
|
-
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
|
-
poly2 = spyrrow.Item("1",[(0, 0), (1, 0), (1, 2), (3, 2), (3, 0), (4, 0), (4, 3), (0, 3), (0, 0)], demand=3, allowed_orientations=[0,90,180,270])
|
|
44
|
-
instance = spyrrow.StripPackingInstance(
|
|
45
|
-
"test", strip_height=4.001, items=[poly1, poly2]
|
|
46
|
-
)
|
|
47
|
-
sol = instance.solve(30)
|
|
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
|