pyextrusion 0.17.0__py3-none-any.whl
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.
- pyextrusion/__init__.py +124 -0
- pyextrusion/_meta.py +8 -0
- pyextrusion/_strict.py +93 -0
- pyextrusion/api.py +78 -0
- pyextrusion/cli.py +538 -0
- pyextrusion/comparison.py +219 -0
- pyextrusion/core.py +1143 -0
- pyextrusion/demand.py +142 -0
- pyextrusion/economics/__init__.py +20 -0
- pyextrusion/economics/basic.py +402 -0
- pyextrusion/engineering/__init__.py +201 -0
- pyextrusion/engineering/extrusion_flow.py +133 -0
- pyextrusion/engineering/materials.py +187 -0
- pyextrusion/engineering/mechanics.py +119 -0
- pyextrusion/engineering/metadata.py +163 -0
- pyextrusion/engineering/power.py +204 -0
- pyextrusion/engineering/press.py +274 -0
- pyextrusion/engineering/pressure.py +335 -0
- pyextrusion/engineering/rheology.py +122 -0
- pyextrusion/engineering/saha_thermal.py +288 -0
- pyextrusion/engineering/thermal.py +486 -0
- pyextrusion/engineering/thermomechanical.py +344 -0
- pyextrusion/errors.py +170 -0
- pyextrusion/fields.py +146 -0
- pyextrusion/io.py +374 -0
- pyextrusion/models.py +1142 -0
- pyextrusion/planning.py +494 -0
- pyextrusion/py.typed +0 -0
- pyextrusion/reporting.py +349 -0
- pyextrusion/sequence.py +321 -0
- pyextrusion/validation.py +245 -0
- pyextrusion-0.17.0.dist-info/METADATA +234 -0
- pyextrusion-0.17.0.dist-info/RECORD +38 -0
- pyextrusion-0.17.0.dist-info/WHEEL +5 -0
- pyextrusion-0.17.0.dist-info/entry_points.txt +2 -0
- pyextrusion-0.17.0.dist-info/licenses/LICENSE +202 -0
- pyextrusion-0.17.0.dist-info/licenses/NOTICE +5 -0
- pyextrusion-0.17.0.dist-info/top_level.txt +1 -0
pyextrusion/__init__.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""PyExtrusion - engineering calculation toolkit for aluminium extrusion."""
|
|
2
|
+
from ._meta import __author__, __description__, __license__, __title__, __url__, __version__
|
|
3
|
+
from .api import calculate_case, calculate_process, calculate_simple, compare_presses
|
|
4
|
+
from .comparison import (
|
|
5
|
+
ProcessComparisonResult, PlanningComparisonResult, ProductionSequenceComparisonResult,
|
|
6
|
+
compare_processes, compare_planning, compare_production_sequences,
|
|
7
|
+
)
|
|
8
|
+
from .demand import (
|
|
9
|
+
AnnualDemandSpec, NormalizedAnnualDemand, AnnualCalculationResult,
|
|
10
|
+
normalize_annual_demand, calculate_annual_demand,
|
|
11
|
+
)
|
|
12
|
+
from .planning import (
|
|
13
|
+
PlanningMode, PlanningRequest, PlanningResult,
|
|
14
|
+
validate_planning_request, calculate_planning,
|
|
15
|
+
)
|
|
16
|
+
from .sequence import (
|
|
17
|
+
ProductionOrder, ProductionSequenceEntry, ProductionSequenceResult,
|
|
18
|
+
calculate_production_sequence,
|
|
19
|
+
)
|
|
20
|
+
from .core import calculate, section_from_linear_weight, extrusion_ratio, extrusion_ratio_status, ram_speed
|
|
21
|
+
from .io import (
|
|
22
|
+
case_from_dict,
|
|
23
|
+
case_to_dict,
|
|
24
|
+
load_case_json,
|
|
25
|
+
load_press_json,
|
|
26
|
+
load_study_json,
|
|
27
|
+
press_from_dict,
|
|
28
|
+
press_to_dict,
|
|
29
|
+
process_from_dict,
|
|
30
|
+
planning_case_from_dict,
|
|
31
|
+
planning_case_to_dict,
|
|
32
|
+
load_planning_case_json,
|
|
33
|
+
save_planning_case_json,
|
|
34
|
+
save_case_json,
|
|
35
|
+
save_press_json,
|
|
36
|
+
save_study_json,
|
|
37
|
+
study_from_dict,
|
|
38
|
+
study_to_dict,
|
|
39
|
+
annual_demand_from_dict, annual_demand_to_dict,
|
|
40
|
+
load_annual_demand_json, save_annual_demand_json,
|
|
41
|
+
production_order_from_dict, production_order_to_dict,
|
|
42
|
+
production_sequence_from_dict, production_sequence_to_dict,
|
|
43
|
+
load_production_sequence_json, save_production_sequence_json,
|
|
44
|
+
)
|
|
45
|
+
from .models import (
|
|
46
|
+
PressSpec, ProfileSpec, ProcessSpec, PlanningCase, ProductionSpec, StudyCase, StudyInput,
|
|
47
|
+
CalculationResult, ProcessResult, ComparisonResult, ConfigurationResult,
|
|
48
|
+
StatusResult, GeometryResult, BilletResult, ProductionResult, ScrapResult,
|
|
49
|
+
ProductivityResult, TimingResult, ProcessTraceResult,
|
|
50
|
+
SawSpec, ButtRule, ProfileType, CanonicalProfileType, PROFILE_TYPE_ALIASES,
|
|
51
|
+
PROFILE_TYPE_VALID_VALUES, normalize_profile_type,
|
|
52
|
+
)
|
|
53
|
+
from .reporting import (
|
|
54
|
+
format_annual_result, format_comparison, format_press, format_result,
|
|
55
|
+
format_planning_result, format_production_sequence,
|
|
56
|
+
format_process_comparison, format_planning_comparison,
|
|
57
|
+
format_production_sequence_comparison,
|
|
58
|
+
)
|
|
59
|
+
from .fields import FieldInfo, FIELD_GLOSSARY, RESULT_SECTIONS, describe_field, list_fields
|
|
60
|
+
from .errors import (
|
|
61
|
+
ErrorInfo, ERROR_CATALOG, PyExtrusionError, InvalidPressConfigurationError,
|
|
62
|
+
InvalidProfileInputError, InvalidProductionInputError, UnknownResultFieldError,
|
|
63
|
+
UnsupportedSchemaVersionError, InvalidAnnualDemandError, InputFileError,
|
|
64
|
+
UnknownResultSectionError, InvalidResultSelectionError, InvalidStudyAdjustmentError,
|
|
65
|
+
InvalidPlanningRequestError, InvalidProductionSequenceError, InvalidComparisonError,
|
|
66
|
+
get_error_info, list_error_info,
|
|
67
|
+
)
|
|
68
|
+
from .validation import (
|
|
69
|
+
ValidationMessage,
|
|
70
|
+
validate_case,
|
|
71
|
+
validate_case_json,
|
|
72
|
+
validate_planning_case,
|
|
73
|
+
validate_planning_case_json,
|
|
74
|
+
validate_press,
|
|
75
|
+
validate_press_json,
|
|
76
|
+
validate_study,
|
|
77
|
+
validate_study_json,
|
|
78
|
+
validate_annual_demand, validate_annual_demand_json,
|
|
79
|
+
validate_production_sequence_json,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
Press = PressSpec
|
|
83
|
+
Profile = ProfileSpec
|
|
84
|
+
Process = ProcessSpec
|
|
85
|
+
Production = ProductionSpec
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"calculate", "calculate_case", "calculate_process", "calculate_simple", "compare_presses",
|
|
89
|
+
"compare_processes", "compare_planning", "compare_production_sequences",
|
|
90
|
+
"normalize_annual_demand", "calculate_annual_demand", "calculate_planning",
|
|
91
|
+
"validate_planning_request", "calculate_production_sequence",
|
|
92
|
+
"section_from_linear_weight", "extrusion_ratio", "extrusion_ratio_status", "ram_speed",
|
|
93
|
+
"Press", "Profile", "Process", "Production", "PressSpec", "ProfileSpec", "ProcessSpec", "PlanningCase", "ProductionSpec",
|
|
94
|
+
"StudyCase", "StudyInput", "CalculationResult", "ProcessResult", "ComparisonResult", "ConfigurationResult",
|
|
95
|
+
"StatusResult", "GeometryResult", "BilletResult", "ProductionResult", "ScrapResult",
|
|
96
|
+
"ProductivityResult", "TimingResult", "ProcessTraceResult",
|
|
97
|
+
"SawSpec", "ButtRule", "ProfileType", "CanonicalProfileType", "PROFILE_TYPE_ALIASES",
|
|
98
|
+
"PROFILE_TYPE_VALID_VALUES", "normalize_profile_type",
|
|
99
|
+
"AnnualDemandSpec", "NormalizedAnnualDemand", "AnnualCalculationResult",
|
|
100
|
+
"PlanningMode", "PlanningRequest", "PlanningResult",
|
|
101
|
+
"ProductionOrder", "ProductionSequenceEntry", "ProductionSequenceResult",
|
|
102
|
+
"ProcessComparisonResult", "PlanningComparisonResult", "ProductionSequenceComparisonResult",
|
|
103
|
+
"load_press_json", "load_case_json", "load_planning_case_json", "load_study_json",
|
|
104
|
+
"save_press_json", "save_case_json", "save_planning_case_json", "save_study_json",
|
|
105
|
+
"load_annual_demand_json", "save_annual_demand_json",
|
|
106
|
+
"load_production_sequence_json", "save_production_sequence_json",
|
|
107
|
+
"press_from_dict", "process_from_dict", "planning_case_from_dict", "case_from_dict", "study_from_dict", "annual_demand_from_dict",
|
|
108
|
+
"production_order_from_dict", "production_sequence_from_dict",
|
|
109
|
+
"press_to_dict", "planning_case_to_dict", "case_to_dict", "study_to_dict", "annual_demand_to_dict",
|
|
110
|
+
"production_order_to_dict", "production_sequence_to_dict",
|
|
111
|
+
"format_press", "format_result", "format_comparison", "format_annual_result", "format_planning_result", "format_production_sequence",
|
|
112
|
+
"format_process_comparison", "format_planning_comparison", "format_production_sequence_comparison",
|
|
113
|
+
"FieldInfo", "FIELD_GLOSSARY", "RESULT_SECTIONS", "describe_field", "list_fields",
|
|
114
|
+
"ErrorInfo", "ERROR_CATALOG", "PyExtrusionError", "InvalidPressConfigurationError",
|
|
115
|
+
"InvalidProfileInputError", "InvalidProductionInputError", "UnknownResultFieldError",
|
|
116
|
+
"UnsupportedSchemaVersionError", "InvalidAnnualDemandError", "InputFileError",
|
|
117
|
+
"UnknownResultSectionError", "InvalidResultSelectionError", "InvalidStudyAdjustmentError",
|
|
118
|
+
"InvalidPlanningRequestError", "InvalidProductionSequenceError", "InvalidComparisonError",
|
|
119
|
+
"get_error_info", "list_error_info",
|
|
120
|
+
"ValidationMessage", "validate_press", "validate_case", "validate_planning_case", "validate_study",
|
|
121
|
+
"validate_press_json", "validate_case_json", "validate_planning_case_json", "validate_study_json",
|
|
122
|
+
"validate_annual_demand", "validate_annual_demand_json", "validate_production_sequence_json",
|
|
123
|
+
"__title__", "__version__", "__description__", "__author__", "__license__", "__url__",
|
|
124
|
+
]
|
pyextrusion/_meta.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Public project metadata for PyExtrusion."""
|
|
2
|
+
|
|
3
|
+
__title__ = "PyExtrusion"
|
|
4
|
+
__version__ = "0.17.0"
|
|
5
|
+
__description__ = "Engineering calculation toolkit for aluminium extrusion"
|
|
6
|
+
__author__ = "Enrique Calvo Ordonez"
|
|
7
|
+
__license__ = "Apache-2.0"
|
|
8
|
+
__url__ = "https://pyextrusion.com"
|
pyextrusion/_strict.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
E = TypeVar("E", bound=Exception)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _raise(exc_type: type[E], message: str) -> None:
|
|
10
|
+
raise exc_type(message)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def require_number(
|
|
14
|
+
value: Any,
|
|
15
|
+
field: str,
|
|
16
|
+
exc_type: type[E],
|
|
17
|
+
*,
|
|
18
|
+
minimum: float | None = None,
|
|
19
|
+
maximum: float | None = None,
|
|
20
|
+
exclusive_minimum: bool = False,
|
|
21
|
+
allow_none: bool = False,
|
|
22
|
+
) -> float | None:
|
|
23
|
+
if value is None:
|
|
24
|
+
if allow_none:
|
|
25
|
+
return None
|
|
26
|
+
_raise(exc_type, f"{field} is required")
|
|
27
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
28
|
+
_raise(exc_type, f"{field} must be a number, not {type(value).__name__}")
|
|
29
|
+
result = float(value)
|
|
30
|
+
if not math.isfinite(result):
|
|
31
|
+
_raise(exc_type, f"{field} must be a finite number")
|
|
32
|
+
if minimum is not None:
|
|
33
|
+
if exclusive_minimum:
|
|
34
|
+
if not result > minimum:
|
|
35
|
+
_raise(exc_type, f"{field} must be > {minimum:g}")
|
|
36
|
+
elif result < minimum:
|
|
37
|
+
_raise(exc_type, f"{field} must be >= {minimum:g}")
|
|
38
|
+
if maximum is not None and result > maximum:
|
|
39
|
+
_raise(exc_type, f"{field} must be <= {maximum:g}")
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def require_int(
|
|
44
|
+
value: Any,
|
|
45
|
+
field: str,
|
|
46
|
+
exc_type: type[E],
|
|
47
|
+
*,
|
|
48
|
+
minimum: int | None = None,
|
|
49
|
+
maximum: int | None = None,
|
|
50
|
+
allow_none: bool = False,
|
|
51
|
+
) -> int | None:
|
|
52
|
+
if value is None:
|
|
53
|
+
if allow_none:
|
|
54
|
+
return None
|
|
55
|
+
_raise(exc_type, f"{field} is required")
|
|
56
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
57
|
+
_raise(exc_type, f"{field} must be an integer")
|
|
58
|
+
if minimum is not None and value < minimum:
|
|
59
|
+
_raise(exc_type, f"{field} must be >= {minimum}")
|
|
60
|
+
if maximum is not None and value > maximum:
|
|
61
|
+
_raise(exc_type, f"{field} must be <= {maximum}")
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def require_bool(value: Any, field: str, exc_type: type[E]) -> bool:
|
|
66
|
+
if not isinstance(value, bool):
|
|
67
|
+
_raise(exc_type, f"{field} must be true or false")
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def require_string(value: Any, field: str, exc_type: type[E], *, nonempty: bool = True) -> str:
|
|
72
|
+
if not isinstance(value, str):
|
|
73
|
+
_raise(exc_type, f"{field} must be a string")
|
|
74
|
+
if nonempty and not value.strip():
|
|
75
|
+
_raise(exc_type, f"{field} must not be empty")
|
|
76
|
+
return value
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def require_kerf(value: Any, field: str, exc_type: type[E]) -> float:
|
|
80
|
+
result = require_number(value, field, exc_type, minimum=0.0)
|
|
81
|
+
assert result is not None
|
|
82
|
+
if result == 0.0:
|
|
83
|
+
return 0.0
|
|
84
|
+
if result < 3.0 or result > 10.0:
|
|
85
|
+
_raise(exc_type, f"{field} must be 0 mm or between 3 and 10 mm")
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def finite_product(a: float, b: float, field: str, exc_type: type[E]) -> float:
|
|
90
|
+
value = a * b
|
|
91
|
+
if not math.isfinite(value):
|
|
92
|
+
_raise(exc_type, f"{field} is outside the supported finite numeric range")
|
|
93
|
+
return value
|
pyextrusion/api.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
|
|
5
|
+
from .core import calculate, calculate_process as _calculate_process
|
|
6
|
+
from .models import (
|
|
7
|
+
CalculationResult,
|
|
8
|
+
ComparisonResult,
|
|
9
|
+
Complexity,
|
|
10
|
+
PressSpec,
|
|
11
|
+
PlanningCase,
|
|
12
|
+
ProcessResult,
|
|
13
|
+
ProductionSpec,
|
|
14
|
+
ProfileSpec,
|
|
15
|
+
ProfileType,
|
|
16
|
+
StudyCase,
|
|
17
|
+
StudyInput,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def calculate_case(press: PressSpec, case: StudyCase) -> CalculationResult:
|
|
22
|
+
"""Run a reusable StudyCase on one press."""
|
|
23
|
+
return calculate(case.to_study_input(press))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def calculate_process(press: PressSpec, case: PlanningCase) -> ProcessResult:
|
|
27
|
+
"""Evaluate Press + Profile + ProcessSpec without any production quantity."""
|
|
28
|
+
return _calculate_process(case.to_process_input(press))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def compare_presses(presses: Iterable[PressSpec], case: StudyCase) -> ComparisonResult:
|
|
32
|
+
"""Evaluate one case on several presses. Results include the orientative v2.4 productivity index; no final industrial decision is made automatically."""
|
|
33
|
+
press_tuple = tuple(presses)
|
|
34
|
+
if not press_tuple:
|
|
35
|
+
raise ValueError("compare_presses requires at least one press")
|
|
36
|
+
return ComparisonResult(tuple(calculate_case(press, case) for press in press_tuple))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def calculate_simple(
|
|
40
|
+
press: PressSpec,
|
|
41
|
+
*,
|
|
42
|
+
linear_weight_kg_m: float,
|
|
43
|
+
exits: int,
|
|
44
|
+
exit_speed_m_min: float | None = None,
|
|
45
|
+
cut_length_mm: float,
|
|
46
|
+
bars_requested: int,
|
|
47
|
+
profile_type: ProfileType | None = None,
|
|
48
|
+
front_scrap_m: float = 0.0,
|
|
49
|
+
complexity: Complexity = "normal",
|
|
50
|
+
section_per_exit_m2: float | None = None,
|
|
51
|
+
cuts: int | None = None,
|
|
52
|
+
butt_mm: float | None = None,
|
|
53
|
+
multi_billet_front_scrap_m: float | None = None,
|
|
54
|
+
supplement_10_pct: bool = False,
|
|
55
|
+
ram_speed_mm_s: float | None = None,
|
|
56
|
+
) -> CalculationResult:
|
|
57
|
+
"""Low-friction API backed by the same deterministic engine as StudyInput."""
|
|
58
|
+
case = StudyCase(
|
|
59
|
+
profile=ProfileSpec(
|
|
60
|
+
linear_weight_kg_m=linear_weight_kg_m,
|
|
61
|
+
exits=exits,
|
|
62
|
+
profile_type=profile_type,
|
|
63
|
+
section_per_exit_m2=section_per_exit_m2,
|
|
64
|
+
),
|
|
65
|
+
production=ProductionSpec(
|
|
66
|
+
exit_speed_m_min=exit_speed_m_min,
|
|
67
|
+
cut_length_mm=cut_length_mm,
|
|
68
|
+
bars_requested=bars_requested,
|
|
69
|
+
front_scrap_m=front_scrap_m,
|
|
70
|
+
complexity=complexity,
|
|
71
|
+
cuts=cuts,
|
|
72
|
+
butt_mm=butt_mm,
|
|
73
|
+
multi_billet_front_scrap_m=multi_billet_front_scrap_m,
|
|
74
|
+
supplement_10_pct=supplement_10_pct,
|
|
75
|
+
ram_speed_mm_s=ram_speed_mm_s,
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
return calculate_case(press, case)
|