Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .
python -m pip install -e ".[dev]"
- name: Build and test docs
run: |
if [ ${{ github.ref }} == "refs/heads/main" ]; then
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test_package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11']
python-version: ['3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v3
Expand All @@ -25,7 +25,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .
python -m pip install -e ".[dev]"
- name: Test with pytest
run: |
python -m pytest
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class SimpleLine(Line):

# Wire them with buffers
source.connect_to_output(station=process, capacity=3)
process.connect_to_output(station=process, capacity=2)
process.connect_to_output(station=sink, capacity=2)


line = SimpleLine()
Expand Down
119 changes: 47 additions & 72 deletions docs/userguide/core_concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,76 +205,51 @@ updated, this is directly visible in the state of the line.

## Parts and Carriers

In `LineFlow`, every part is transported by a carrier as it
moves through the production line. Hence, carriers act as mobile containers: once a
[`Source`][lineflow.simulation.stations.Source] (or a
[`Magazine`][lineflow.simulation.stations.Magazine]) creates a new carrier, it puts a predefined
sequence of initial parts onto the newly created carrier.
At each station the carrier visits, new parts can be added or existing parts removed, typically at
[`Assembly`][lineflow.simulation.stations.Assembly] stations.

Parts can show different behaviors at different stations. For instance, a certain part may requires
an additional processing time at a given process or has to statisfy a certain condition before it
can be assembled. These specification are fixed when creating the part at the source and can be
given in a `carrier_spec`. For instance, the following parameter can be given when creating a source
station:

### Carriers
In LineFlow, every individual Part is always transported by a Carrier as it
moves through the production line. Carriers act as mobile containers: once a
Source station creates a new Part (or set of Parts), it places them onto a
Carrier, which then follows the sequence of Buffers and Stations. At each step,
the Carrier carries its load of Parts downstream—whether through processing,
assembly, or inspection—until it reaches a Sink. Because Parts never traverse
the line on their own, all material flow, blocking behavior, and routing logic
hinge on Carrier movement and occupancy.

### Parts

1. **Define Part Specifications**:
- Part specifications are defined as a list of dictionaries, where each dictionary contains the attributes of a part. For example:
```python
part_specs = [
{"attribute1": value1, "attribute2": value2},
{"attribute1": value3, "attribute2": value4},
]
```

2. **Initialize the Source Station**:
- When initializing a [`Source`][lineflow.simulation.stations.Source]
station, pass the part specs list to the part_specs parameter:
```python
source = Source(
name="source_name",
part_specs=part_specs,
# other parameters
)
```

3. **Create Parts**:
- The [`create_parts`][lineflow.simulation.stations.Source.create_parts] method of the
[`Source`][lineflow.simulation.stations.Source] class is responsible for
creating parts based on the part_specs attribute. This method iterates
over each dictionary in the part_specs list and creates a [`Part`][lineflow.simulation.movable_objects.Part] object
for each specification:
```python
def create_parts(self):
parts = []
for part_spec in self.part_specs:
part = Part(
env=self.env,
name=self.name + '_part_' + str(self.part_id),
specs=part_spec,
)
self.part_id += 1
part.create(self.position)
parts.append(part)
return parts
```

4. **Assemble Parts on Carrier**:
- Once the parts are created, they can be assembled onto a carrier using the
[`assemble_parts_on_carrier`][lineflow.simulation.stations.Source.assemble_parts_on_carrier]
method:
```python
def assemble_parts_on_carrier(self, carrier, parts):
for part in parts:
carrier.assemble(part)
```

5. **Derive Actions from Part Specifications**:
- Actions can be derived from the new state of the parts. The [`apply`][lineflow.simulation.stations.Station] method in the [`Station`][lineflow.simulation.stations.Station] class can be used to apply these actions:
```python
def apply(self, actions):
self._derive_actions_from_new_state(actions)
self.state.apply(actions)
```

By following these steps, you can create new parts with specific attributes, initialize them in a source station, and derive actions based on their specifications.
```python
carrier_spec = {
"CarrierA": {
"Part1": {
"P1": {"processing_time": 5},
"A1": {"assembly_condition": 10},
}
"Part2": {
"P1": {"processing_time": 1},
"P2": {"processing_time": 3},
"A1": {"assembly_condition": 8},
}
},
"CarrierB": {
"Part1": {
"A1": {"assembly_condition": 10},
}
"Part2": {
"P2": {"processing_time": 3},
}
"Part3": {
"P1": {"processing_time": 1},
"P2": {"processing_time": 3},
"A1": {"assembly_condition": 8},
}
}
}
```

Here, the source creates (randomly) either a carrier of type `CarrierA` or of `CarrierB`. `CarrierA`
has two parts, `Part1` and `Part2`, each with their own specifications. For instance, `Part1`
consues an additional processing time of 5 time stepts at station `P1` and needs to be assembled at
station `A1` within `10` time steps from its creation. Similarly, `Part2` has a processing time of 1
at `P1`, 3 at `P2`, and an assembly condition of 8 at `A1`.
5 changes: 3 additions & 2 deletions docs/userguide/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SimpleLine(Line):
Source(
name='Source',
processing_time=5,
unlimited_carriers=True,
buffer_out=buffer,
position=(100, 300),
)
Expand All @@ -30,9 +31,9 @@ from lineflow.simulation import Line, Source, Sink
class SimpleLine(Line):

def build(self):
source = Source(name='Source', processing_time=5, position=(100, 300))
source = Source(name='Source', processing_time=5, position=(100, 300), unlimited_carriers=True)
sink = Sink('Sink', position=(600, 300))
sink.connect_to_output(station=source, capacity=6)
source.connect_to_output(station=sink, capacity=6)
```


Expand Down
3 changes: 2 additions & 1 deletion lineflow/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
from lineflow.examples.worker_assignment import WorkerAssignment #noqa
from lineflow.examples.double_source import DoubleSource #noqa
from lineflow.examples.waiting_time import WaitingTime #noqa
from lineflow.examples.complex_line import ComplexLine #noqa
from lineflow.examples.complex_line import ComplexLine #noqa
from lineflow.examples.part_dependence import PartDependentProcessLine #noqa
10 changes: 7 additions & 3 deletions lineflow/examples/complex_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,13 @@ def build(self):
unlimited_carriers=True,
carrier_capacity=1,
actionable_waiting_time=True,
part_specs=[{
"assembly_condition": self.assembly_condition,
}],
carrier_specs={
'Carrier': {
'Part': {
f'A{i}': {"assembly_condition": self.assembly_condition} for i in range(self.n_assemblies)
}
}
}
)

switch = Switch(
Expand Down
23 changes: 15 additions & 8 deletions lineflow/examples/component_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ def build(self):
'A1',
processing_time=4,
position=(500, 300),
part_specs=[{
"assembly_condition": 40
}],
carrier_specs={
'A': {
'ComponentA': {
'C21': {"assembly_condition": 40},
'C22': {"assembly_condition": 40},
}
}
},
unlimited_carriers=True,
carrier_capacity=1,
)



switch1 = Switch('S1', alternate=True, position=(200, 300),)
switch2 = Switch('S2', alternate=True, position=(300, 300))
switch3 = Switch('S3', alternate=True, position=(580, 300))
Expand Down Expand Up @@ -95,9 +98,13 @@ def build(self):
'A2',
processing_time=5,
position=(600, 400),
part_specs=[{
"assembly_condition": 400
}],
carrier_specs={
'B': {
'Component_B': {
'C5': {"assembly_condition": 400},
}
}
},
unlimited_carriers=True,
carrier_capacity=1,
)
Expand Down
48 changes: 48 additions & 0 deletions lineflow/examples/part_dependence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from lineflow.simulation import (
WorkerPool,
Source,
Sink,
Line,
Process,
)


class PartDependentProcessLine(Line):
def build(self):
source = Source(
name='Source',
processing_time=10,
position=(100, 200),
unlimited_carriers=True,
carrier_capacity=1,
carrier_min_creation=10,
carrier_specs={
'Type_A': {
'Part1': {
'P1': {'extra_processing_time': 20},
'P2': {'extra_processing_time': 0}
}
},
'Type_B': {
'Part1': {
'P1': {'extra_processing_time': 0},
'P2': {'extra_processing_time': 20}
}
},
},
)

pool = WorkerPool(name='Pool', n_workers=4)

p1 = Process('P1', processing_time=10, position=(350, 200), worker_pool=pool)
p2 = Process('P2', processing_time=10, position=(700, 200), worker_pool=pool)
sink = Sink('Sink', position=(850, 200))

p1.connect_to_input(source, capacity=15)
p2.connect_to_input(p1, capacity=15)
sink.connect_to_input(p2)


if __name__ == '__main__':
line = PartDependentProcessLine(realtime=True, factor=0.8)
line.run(simulation_end=1000, visualize=True)
Loading